缓存内存如何工作?

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

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的Ulrich Drepper和glibc成名的一篇非常好的论文, 每位程序员应该了解的关于记忆的内容 。 一节详细讨论了缓存。 例如,在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中获取它们,因此性能会降低。