C中的矩阵计算

时间:2017-10-31 12:39:15

标签: c matrix memory

我最近注意到,在C中访问矩阵的方式看似微不足道的变化会对性能产生很大影响。 例如,让我们想象我们有这两个C代码片段。这一个:

for(i = 0; i < 2048; i++)
{
    for(j = 0; j < 2048; j++) {
            Matrix[i][j] = 9999;    
    }
}

这一个:

for(j = 0; j < 2048; j++)
{
    for(i = 0; i < 2048; i++) {
            Matrix[i][j] = 9999;    
    }
}

第二个版本比第一个版本慢2倍。为什么?我认为它与内存管理有关:在每个循环中,第一个版本访问内存中彼此相邻的位置,而第二个版本必须&#34;跳转&#34;每个循环中的不同区域。 这种直觉是对的吗? 此外,如果我使矩阵变小(例如64x64),那么性能没有差别。为什么? 如果有人能提供直观而严谨的解释,我将不胜感激。 顺便说一下,我正在使用Ubuntu 14.04 LTS。

4 个答案:

答案 0 :(得分:5)

        for(i=0;i<2048;i++)
        {
                for(j=0;j<2048;j++) {
                        Matrix[i][j]=9999;    
                }
        }

此表单使用L1,L2和L3缓存对齐。当您使用j循环Matrix[i][j]时,元素Matrix[i][0]Matrix[i][1] ... a.s.o。在连续的地址处对齐(实际上在sizeof(Matrix[i][0]))的地址不同,因此访问Matrix[i][0]会在缓存中引入下一个变量Matrix [i] [1]。

另一方面,

        for(j=0;j<2048;j++)
        {
                for(i=0;i<2048;i++) {
                        Matrix[i][j]=9999;    
                }
        }

内部循环以Matrix[0][j]Matrix[1][j] ... a.s.o的顺序访问。 Matrix[1][j]的地址为Matrix[0][j]+2048*sizeof(Matrix[0][0]) - 假设您为数组Matrix[0]分配了2048个条目。

所以Matrix[0][j]位于另一个缓存块而不是Matrix[1][j],要求提取在RAM中进行访问而不是缓存。

在第二种情况下,每次迭代都可以访问RAM。

答案 1 :(得分:3)

&#34; 它是缓存!它是缓存!&#34;

要想象它,将内存视为线性数组...

通过定义2D数组:

uint8_t Matrix[4][4]

你只是说:

allocate 16 bytes, and access them as a 2D array, 4x4

这个例子假设一个4字节的缓存,简单起见:

2D array in memory

如果CPU的缓存只能容纳4个字节,那么接近[0][0][1][0][2][0],...表单中的数组将导致缓存未命中每次访问 - 要求我们访问RAM(这是昂贵的)16次!

接近[0][0][0][1][0][2],...表单中的数组将允许完整访问2D数组,只有4个缓存未命中。

这个例子非常简单 - 现代系统几乎肯定会有一个L1和L2缓存,而且很多人现在也在实现L3缓存。

随着处理器内核越来越远,内存越来越大。例如:

  1. L1缓存(小,非常快)
  2. 二级缓存
  3. L3缓存(?)
  4. RAM
  5. 持久存储(例如:HDD - 巨大,但相对,非常,非常慢)。

答案 2 :(得分:2)

它与locality of referenceCPU cache相关。所以它主要是针对特定处理器的(并没有特定的操作系统)。

高速缓存未命中可能非常昂贵(typically,对DRAM模块上的数据的访问需要数百纳秒 - 足以从L1 I-cache执行一百个机器指令),但是对L1高速缓存的访问只需要一个或者几纳秒)。

另请阅读thisthat。有时(但并非总是)使用__builtin_prefetch 可能可以提高性能(但通常GCC编译器可以通过适当地发出PREFETCH machine instructions来优化您的优化。但严重或过于频繁地使用__builtin_prefetch会损害性能。

不要忘记在编译器中启用优化,因此至少在基准测试之前使用gcc -Wall -O2 -march=native进行编译(甚至是-O3而不是-O2 ...)。

答案 3 :(得分:2)

关于缓存的全部内容。在后者中,您基本上是按顺序读取内存。在第一种情况下,你会在每次阅读之间做很长的跳跃。

计算机中存在将附近数据存储在读取中的电路,因为很可能很快就会读取附近的数据。您无法控制这些电路的工作方式。您所能做的就是根据行为调整代码。