本文摘要(由AI生成):
本文讨论了内存性能对软件运行速度的影响,指出优秀软件应充分利用内存层次结构。通过比较两段访问二维数组元素的代码,文章指出,即使是很小的代码改动,如交换数组下标,也可能导致运行时性能产生显著差异。这是因为内存访问的连续性和局部性对性能至关重要。此外,文章还介绍了颠簸现象,即由内存容量不足或程序缺乏引用局部性导致的性能下降。因此,程序员应仔细研究内存访问模式,优化代码,以显著提升软件性能。
有意识地根据内存性能设计出来的软件运行要快得多。尽管对于大多数程序来说,系统提供的缓存和分页功能已足好了,但是我们仍然追求运行更快的程序,哪怕系统中没有缓存。最优秀的软件一定会最大限度地利用内层次结构。来看下面的2段程序:
//片段1
//创建一个二维数组并赋值
int array[256][256];
for( i=0; i<256; ++i ) {
for( j=0; j256; ++j) {
array[j][i]=i*j;
}
}
//片段2
//创建一个二维数组并赋值
int array[256][256];
for( i=0; i<256; ++i ) {
for( j=0; j256; ++j) {
array[i][j]=i*j;
}
}
这两段代码的唯一区别就是在访问数组元素时交换了下标i和j。这么小的改动会导致运行时一个(甚至两个)数量级的性能差别!要理解其中的原因,就要知道在C语言中,内存中的二维数组采用的是行优先顺序。因此,第二段代码访问的是内存中连续的单位,具备引用的空间局部性。而第一段代码访问数组元素的顺序是这样的:array[0][0],array[1][0],array[2][0],...,array[254][0]array[255][0],array[0][1],...
如果整型是4个字节,那么按照上面顺序访问的是偏移量为 0、1024、2048、3072等位置的双字值,访问显然不是连续的。这段代码大概率只能将n个整数加载到n路组相联缓存中,然后很快就会出现颠簸,因为后续的数组元素为了避免被覆盖不得不将其从缓存复 制到主存中。第二段代码则不会出现颠簸。假定缓存采用了64字节的缓存行,对于第二段代码来说,一个缓存行一次可以存储 16个整数,然后才会从主存中加载另一个缓存行置换这一块数据。这样,第二段代码每次从内存载入缓存行的数据可以访问16次加载的成本被分摊了,而第一段代码这样的一次内存访问就要加载一次.
总之,我们应该仔细研究程序的内存访问模式,并相应调整代码。花几个小时用手工优化的汇编代码重写程序可能获得 10%的性能提升。但是改变程序访问内存的方式让性能提升一个数量级,也不是不可能。
颠簸是一种性能退化,它会导致系统性能整体下降到内存层次结构中的主存甚至磁盘驱动器这些较慢的层次。导致颠簸的原因主要有两个: