一字之差,程序性能却相去甚远

2023-09-11 18:36:07 浏览数 (1)

有意识地根据内存性能设计出来的软件运行要快得多。尽管对于大多数程序来说,系统提供的缓存和分页功能已足好了,但是我们仍然追求运行更快的程序,哪怕系统中没有缓存。最优秀的软件一定会最大限度地利用内层次结构。来看下面的2段程序:

代码语言:javascript复制
//片段1
//创建一个二维数组并赋值
int array[256][256];
for( i=0; i<256;   i ) {
    for( j=0; j256;   j) {
        array[j][i]=i*j;
    }
}

代码语言:javascript复制
//片段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%的性能提升。但是改变程序访问内存的方式让性能提升一个数量级,也不是不可能。

颠簸是一种性能退化,它会导致系统性能整体下降到内存层次结构中的主存甚至磁盘驱动器这些较慢的层次。导致颠簸的原因主要有两个:

  • 内存层次结构中某个级别的内存容量不足以容纳程序的缓存行工作 集或页工作集
  • 程序不具备引用局部性。

0 人点赞