Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/performance/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 在二维数组上迭代的嵌套循环的哪个顺序更有效_C_Performance_For Loop_Cpu Cache - Fatal编程技术网

C 在二维数组上迭代的嵌套循环的哪个顺序更有效

C 在二维数组上迭代的嵌套循环的哪个顺序更有效,c,performance,for-loop,cpu-cache,C,Performance,For Loop,Cpu Cache,在时间(缓存性能)方面,在二维数组上迭代嵌套循环的以下哪种顺序更有效?为什么? inta[100][100]; 对于(i=0;i在您的情况下(填充所有数组1值),这将更快: for(j = 0; j < 100 * 100; j++){ a[j] = 10; } 第一种方法稍好一些,因为被分配到的单元彼此相邻 第一种方法: [ ][ ][ ][ ][ ] .... ^1st assignment ^2nd assignment [ ][ ][ ][ ][ ]

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

inta[100][100];
对于(i=0;i在您的情况下(填充所有数组1值),这将更快:

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

第一种方法稍好一些,因为被分配到的单元彼此相邻

第一种方法:

[ ][ ][ ][ ][ ] ....
^1st assignment
   ^2nd assignment
[ ][ ][ ][ ][ ] ....
^101st assignment
第二种方法:

[ ][ ][ ][ ][ ] ....
^1st assignment
   ^101st assignment
[ ][ ][ ][ ][ ] ....
^2nd assignment

这种微观优化依赖于平台,因此您需要分析代码,以便能够得出合理的结论

  • 对于数组[100][100]-它们都是相同的,如果一级缓存大于100*100*sizeof(int)==10000*sizeof(int)==[通常]40000。请注意,-100*100整数应该足够多的元素来显示差异,因为一级缓存只有32k

  • 编译器可能会同样优化这段代码

  • 假设没有编译器优化,并且矩阵不适合一级缓存-由于缓存性能[通常],第一个代码会更好。每次在缓存中找不到元素时,您都会得到一个-并且需要转到RAM或二级缓存[速度要慢得多]。将元素从RAM带到缓存[缓存填充]是分块完成的[通常为8/16字节]-因此,在第一个代码中,您最多可以获得未命中率
    1/4
    [假设16字节缓存块,4字节整数],而在第二个代码中,它是无界的,甚至可以是1。在第二个代码快照中-缓存中已经存在的元素[插入到缓存中填充相邻元素]-被取出,你会得到一个冗余缓存未命中

    • 这与实现缓存系统时使用的一般假设密切相关。第一个代码遵循此原则,而第二个代码则不遵循此原则,因此第一个代码的缓存性能将优于第二个代码
  • 结论:
    对于所有的缓存实现,我知道第一个不会比第二个差,它们可能是相同的——如果根本没有缓存或者所有的数组完全适合缓存,或者由于编译器优化。

    < P>考虑C++是行的主要,我相信第一个方法会更快一点。一维数组和性能取决于使用行主数组或列主数组访问它。在第二个代码片段中,
    j
    在每次迭代中的更改都会生成一个具有低空间局部性的模式。请记住,在幕后,数组引用会计算:

    ( ((y) * (row->width)) + (x) ) 
    
    考虑一个简化的一级缓存,它只有足够的空间容纳我们阵列的50行。对于前50次迭代,您将为50次缓存未命中支付不可避免的成本,但接下来会发生什么?对于从50到99次的每次迭代,您仍将缓存未命中,并且必须从二级缓存(和/或RAM等)获取数据。然后,
    x
    更改为1,而
    y
    重新开始,导致另一次缓存未命中,因为数组的第一行已从缓存中移出,依此类推

    第一个代码段没有这个问题。它以行主顺序访问数组,从而获得更好的局部性-每行最多只需支付一次缓存未命中(如果循环启动时缓存中不存在数组的行)


    这就是说,这是一个非常依赖于体系结构的问题,因此您必须考虑细节(一级缓存大小、缓存线大小等)得出结论。您还应该测量这两种方法,并跟踪硬件事件,以获得具体的数据来得出结论。

    这是有关
    缓存线跳转的一个经典问题


    大多数情况下,第一种方法更好,但我认为确切的答案是:这取决于,不同的体系结构可能会有不同的结果。

    在第二种方法中,缓存未命中,因为缓存存储了连续的数据。
    因此,第一种方法比第二种方法更有效。

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

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


    嗯,我的答案是一般性的。对于非常简单的循环,就像#1或#2一样,可能会使用更简单的积极编译器优化(对任何差异进行分级),而且编译器通常能够使用STERID-1对外部循环(特别是使用#pragma simd或类似工具)自动向量化#2.

    第一个选项更好,因为我们可以将
    a[i]存储在第一个循环中的一个临时变量中,然后在其中查找j索引。从这个意义上讲,它可以说是缓存变量。

    您需要使用指针来实现这一点,您不能直接以这种方式分配。很抱歉,您是一个PITA,但您需要将其双重解引用:)小注:使用“++i”而不是“i++”。它更快(虽然对于数字而言,与“i++”的差异非常小,而不是STL迭代器)。@Raxillan-这在现代处理器和编译器中不再适用,具体取决于实际语言。@Raxillan这是错误的。它们同样有效,除非您使用的是70年代的编译器。@Raxillan在本例中,不是。优化器足够聪明,知道它不需要副本。@Raxillan为什么要这样做?新值和旧值都不会在同一个表达式中使用,编译器知道这一点。那为什么它会有不同呢?K。。。。所以这意味着第一个总是有效的。。。我们可以更快地访问元素…@SachinMhetre:For
    [ ][ ][ ][ ][ ] ....
    ^1st assignment
       ^2nd assignment
    [ ][ ][ ][ ][ ] ....
    ^101st assignment
    
    [ ][ ][ ][ ][ ] ....
    ^1st assignment
       ^101st assignment
    [ ][ ][ ][ ][ ] ....
    ^2nd assignment
    
    ( ((y) * (row->width)) + (x) )