如何编写缓存友好的代码

本文探讨了存储器层次结构中的缓存及其重要性,解释了时间局部性和空间局部性如何影响缓存命中率。通过分析示例代码,展示了不同访问顺序对缓存效率的影响,强调了编写对缓存友好的代码对于提升程序性能的意义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、存储器层次结构中的缓存

存储器的层次结构自顶向下通常是:寄存器——高速缓存L1——高速缓存L2——高速缓存L3——主存——本地磁盘——远程二级存储 (比如分布式文件系统、web服务器)。自顶向下,存储设备的速度越来越慢,价格越来越便宜。

上述层次结构中,每一层都缓存来自较低一层的数据对象。本地磁盘可以作为通过网络从远程磁盘取出的文件 (比如web页面) 的缓存,主存可以作为本地磁盘数据的缓存,高速缓存可以作为主存的缓存,依次类推。每一层的缓存包含了下一层数据子集的副本。

  • 缓存命中:当程序需要第k+1层的某个数据对象d时,首先会在第k层的存储设备的一个块中查找d。如果d刚好在第k层,那么就是缓存命中。
  • 缓存不命中:如果第k层中没有数据d,就是缓存不命中,此时需要从第k+1层取出包含数据d的块,放到第k层,如果放之前第k层的缓存已满,就需要按照一定的策略 (比如最近最少使用LRU) 替换现存的一个块。

2、利用局部性写出对缓存友好的代码

  • 时间局部性。由于时间局部性,同一个数据对象可能多次被使用。当一个数据对象在第一次不命中时被复制到缓存中,我们就会期望后面对这个对象有一系列的访问命中。这样可以加速对数据的访问。
  • 空间局部性。块通常包含多个数据对象。当把包含某一个数据对象的块复制到高一层的缓存设备中,我们就会期望后面可以对该块中其他数据对象进行访问。

示例代码1:

/*
* 函数 sumArrayRows 的目的是求得二维数组 a 的每个元素的和
*/
int sumArrayRows(int a[M][N]){
  int i, j, sum = 0;
  
  for(i = 0; i < M; ++i){
    for(j = 0; j < N; ++j){
      sum += a[i][j];
    }
  }
  return sum;
}

C语言是以行优先的顺序存储数组。假设高速缓存块共16个字节,也就是可以存放4个int类型的整数。本例中,数组的大小超过了缓存的容量 (实际中更可能是这种情况) 。

现在来看看缓存的命中情况。

首先,我们来看局部变量i,j,sum,由于一直在循环内都被使用,因此具有良好的时间局部性,编译器会把它们缓存在寄存器中。

然后,我们来看对二维数组的访问情况。访问a[0] [0],缓存未命中,把包含a[0] [0]、a[0] [1]、a[0] [2]、a[0] [3]的块复制到缓存中,然后访问a[0] [1]、a[0] [2]、a[0] [3]时,都获得缓存命中。接下来访问a[0] [4],缓存不命中,把包含a[0] [4]、a[0] [5]、a[0] [6]、a[0] [7]的块复制到缓存中,然后访问a[0] [5]、a[0] [6]、a[0] [7]时,都获得缓存命中依次类推…

在这里插入图片描述

缓存不命中率为1/4

接下来看一下示例代码2,在计算结果上与示例代码1一致。

示例代码2:

/*
* 函数 sumArrayRows 的目的是求得二维数组 a 的每个元素的和
*/
int sumArrayRows(int a[M][N]){
  int i, j, sum = 0;
  
  for(j = 0; i < M; ++i){
    for(i = 0; j < N; ++j){
      sum += a[i][j];
    }
  }
  return sum;
}

示例代码2更改了二维数组中元素的访问顺序,是一列一列的访问。这会导致缓存全部不命中。

访问a[0] [0],缓存未命中,把包含a[0] [0]、a[0] [1]、a[0] [2]、a[0] [3]的块复制到缓存中。然后访问a[1] [0],缓存不命中,把包含把包含a[1] [0]、a[1] [1]、a[1] [2]、a[1] [3]的块复制到缓存中,原先的块被替换。然后访问a[2] [0],缓存依然不命中,依次类推…

如果整个数组都可以被放入到缓存中,此时获得的是1/4的缓存不命中。但更可能是,数组比高速缓存大,这样缓存将全部不命中。

在这里插入图片描述

小结

1)一旦从存储器中读入一个数据对象,应该利用程序的时间局部性,尽可能多地使用它;

2)按照数据对象存储在内存中的顺序、以步长为1依次读数据,利用空间局部性,提升程序的性能。

参考资料:《深入理解计算机系统》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值