今天当我在计算机组织课上时,老师谈到了一些有趣的事情。谈到为什么缓存有效时,他说:

for (i=0; i<M; i++)
   for(j=0; j<N; j++)
      X[i][j] = X[i][j] + K; //X is double(8 bytes)

用第二行改变第一行是不好的。你对此有何看法?为什么会这样?

有帮助吗?

解决方案

参考地点。因为数据是按行存储的,所以对于每一行,j列都在相邻的存储器地址中。操作系统通常会将整个页面从内存加载到缓存中,相邻的地址引用可能会引用同一页面。如果你通过内部循环中的行索引递增,那么这些行可能会在不同的页面上(因为它们被每个j分开),并且缓存可能必须不断地引入并丢弃内存页面数据。这称为颠簸,对性能不利。

在实践中,对于较大的现代缓存,行/列的大小在发挥作用之前需要相当大,但它仍然是很好的做法。

[编辑]以上答案特定于C,可能因其他语言而异。我所知道的唯一不同的是FORTRAN。 FORTRAN按列主要顺序存储事物(上面是行主要部分),更改FORTRAN中语句的顺序是正确的。如果您想/需要效率,了解您的语言如何实现数据存储非常重要。

其他提示

Red Hat和glibc成名的Ulrich Drepper发表了一篇非常好的论文,每位程序员应该做些什么了解记忆。一节非常详细地讨论了缓存。例如,在SMP系统中存在缓存效应,其中CPU最终会来回摧毁修改后的缓存线的所有权,从而极大地损害性能。

这就像是因为像地方这样的缓存。访问的相同数量的存储器,但间隔更远,将击中不同的“线”。缓存,或甚至可能完全错过缓存。因此,只要您有选择,就可以组织数据,以便及时在彼此附近发生的访问也可以在空间中进行。这会增加缓存命中的几率,并为您提供更高的性能。

当然有很多关于这个主题的信息,例如参见这个维基百科关于地方的条目参考。或者,我猜,你自己的课程教科书。 :)

在C中,n维矩阵是行主要,意味着矩阵的最后一个索引表示内存中的相邻空格。这与其他一些语言(FORTRAN)不同,后者是列专业。在FORTRAN中,迭代这样的2D矩阵更有效:

do jj = 1,N
  do ii = 1,M
    x(ii,jj) = x(ii,jj) + K;
  enddo
enddo

高速缓存是非常快速且非常昂贵的内存,靠近CPU。 CPU不是每次从RAM中获取一小段数据,而是取出一大块数据并将其存储在缓存中。赌注是,如果您只读取一个字节,那么您读取的下一个字节可能就在它之后。如果是这种情况,那么它可以来自缓存。

通过按原样布置循环,可以按照存储在内存中的顺序读取字节。这意味着它们位于缓存中,并且可以由CPU非常快速地读取。如果你换了第1行和第2行,那么你就会读到每一行“N”。每次循环的字节数。您正在读取的字节在内存中不再连续,因此它们可能不在缓存中。 CPU必须从(较慢的)RAM中获取它们,因此性能会降低。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top