关于C#:用于迭代2D数组的嵌套循环的哪种排序更有效

Which ordering of nested loops for iterating over a 2D array is more efficient

本问题已经有最佳答案,请猛点这里访问。

在时间(缓存性能)方面,在二维数组上迭代的下列嵌套循环顺序中,哪一个更有效?为什么?

1
2
3
4
5
6
7
8
9
int a[100][100];

for(i=0; i<100; i++)
{
   for(j=0; j<100; j++)
   {
       a[i][j] = 10;    
   }
}

1
2
3
4
5
6
7
for(i=0; i<100; i++)
{
   for(j=0; j<100; j++)
   {
      a[j][i] = 10;    
   }
}


第一个方法是稍长的更好,为细胞被分配到下一层模式的每个其他。 </P >

第一个方法: </P >

1
2
3
4
5
[ ][ ][ ][ ][ ] ....
^1st assignment
   ^2nd assignment
[ ][ ][ ][ ][ ] ....
^101st assignment

第二个方法: </P >

1
2
3
4
5
[ ][ ][ ][ ][ ] ....
^1st assignment
   ^101st assignment
[ ][ ][ ][ ][ ] ....
^2nd assignment


  • 用于阵列[ 100 ] [ 100 ]他们是同样的纸,如果L1高速缓存是较大的前100×100×sizeof(Int)= = 10000×sizeof(Int)= = [通常] 4万。注意,在桑迪桥100×100 integers应该是足够的元素,看到的差分的,因为在L1高速缓存是只读的32K。 </P >

  • 编译器将probably optimize这同样的全代码 </P >

  • assuming好的编译器的优化,和矩阵并不适合在L1高速缓存中的第一个代码是由于更好的高速缓存的性能[通常]。每一次一元是没有发现在高速缓存中的缓存,你得到一个小姐和需要去的L2高速缓存RAM或[这是多slower ]。以元素从RAM的高速缓存的高速缓存[填充] IS DONE在块(通常8 / 16字节),那么在第一个代码,在你得到最小姐率1/4[ assuming 16字节的高速缓存块的4字节的整数],而在第二次编码,它是可以unbounded,和夏娃的1。在第二单元的代码元素,这是已经在高速缓存中的缓存[ inserted填充的相邻元素]都是taken),和你得到一个磁盘高速缓存的小姐。 </P >

    • 这是closely相关的地方性原则,这是通用的用于当执行自冒的缓存系统。第一本关于这些原则,而第二次不t SO的高速缓存的性能的第一个更好的将是那些在第二次。
  • 结论: 所有的高速缓存implementations I AM感知中的第一个将是诺特及其当时的第二次。他们可能是一样的,如果有好的缓存在全全或数组的适合在完全缓存或由于编译器优化。 </P >


    这大概是微优化平台的依赖性,你将需要写入的代码是为了能画一个合理的结论。 </P >


    在你的第二段,在变化中j在每个迭代产生一个低的空间格局与局限性。记住,这背后的scenes,安computes阵列的参考: </P >

    1
    ( ((y) * (row->width)) + (x) )

    考虑一个简化的L1高速缓存,已足够的空间用于只读rows 50我们的阵列。方法的第一个iterations 50,你将支付的费用为50 unavoidable缓存misses,但当时怎么发生的?for each迭代从50至99,你将它缓存fetch和小姐要从L2(和/或RAM等)。然后,x变化到1和y开始领先过,到另一个缓存小姐,因为你的第一行阵列已被evicted从高速缓存,即死。 </P >

    第一个代码片段并不有这个问题。它accesses数组中的行的大阶,这achieves更好的地方性你只读要付费的方法misses缓存在最一次(如果一个行你的阵列是目前不在高速缓存的时间环,每行的开始)。 </P >

    那被说,这是一个非常建筑相关的问题,所以你可能要采取的积极性consideration的specifics(L1缓存的大小,高速缓存行的大小,等。)来画的结论。你应该也测量了两种方式和保持轨道中的硬件事件有混凝土的结论来自数据的画。 </P >


    考虑的C + +是主要的行,我相信第一个方法是要去是一个位的更快。在一个二维阵列的存储器是在一个单一的represented二维阵列的性能和它在accessing depends either使用行或列梅杰少校 </P >


    在第二次试验,高速缓存的小姐,因为单纯的数据高速缓存的商店。 hence的第一个方法是高效的,比第二次的方法。 </P >


    这是一个经典的问题,关于cache line bouncing </P >

    在大多数时间的第一个是更好的,但我认为对的答案是:它exactly depends,不同的建筑也许有不同的结果。 </P >


    在你的案例(全1的值填充数组),那将是更快。 </P >

    1
    2
    3
       for(j = 0; j < 100 * 100; j++){
          a[j] = 10;
       }

    你可以和它的治疗a为2维阵列。 </P >

    编辑: 作为目前sharet这些,你可以做的,如果你的a是declared通说的: </P >

    1
    2
    3
    4
    int **a = new int*[100];
    for(int i = 0; i < 100; i++){
        a[i] = new int[100];
    }


    一般来说,更好的位置(大多数响应者注意到)只是循环1性能的第一个优势。

    第二个(但相关的)优势是,对于类似于1的循环,编译器通常能够使用step-1内存访问模式高效地自动向量化代码(step-1意味着在下一次迭代中有一个接一个的数组元素访问)。相反,对于类似2的循环,自动矢量化通常不会很好地工作,因为内存中没有连续的step-1迭代访问连续的块。

    嗯,我的回答是一般性的。对于非常简单的循环,如1或2,可能会使用更简单的攻击性编译器优化(对任何差异进行分级),而且编译器通常能够自动向化2,并为外部循环使用step-1(特别是使用pragma simd或类似工具)。


    第一个选项更好,因为我们可以在第一个循环中存储a[i] in a temp variable,然后在其中查找j索引。从这个意义上说,它可以被称为缓存变量。