为什么需要高速缓存
{% note default %}
我们知道,如果要执行一个程序,首先得将一个可执行文件加载到内存当中。那么问题来了,假如我程序中是要对一个数组进行操作,而我数组的数据又是放在内存上的,那么CPU对每一个元素进行操作的时候,都要先把数据加载到寄存器上,对寄存器进行操作,然后再存储到内存。而CPU到内存的这段时间就好比每次上课的时候回家拿书,我能不能把我需要的书(最近访问的数据)都先缓存到我的小书包里,每次上课我就从我的小书包里取出我需要的数据进行操作就OK了。
为什么需要高速缓存
所以高速缓存SRAM介于寄存器与内存之间,就是因为处理器发展到现在,速度越来越快,而现代计算频繁地使用基于SRAM的高速缓存,就是为了弥补处理器与内存之间的差距,而这种方法行之有效就是因为接下来要讲的应用程序的一个称为局部性 的基本属性。
什么是局部性
局部性是什么呢?
之前在优化程序性能讲过,在循环里面尽量用局部变量累计值,或者是将函数调用移出循环外,用一个临时量代替,原因是访问一个局部变量要比从内存中(加载,取值,存储)或是过程调用要快得多(消除了每次循环迭代中从内存中读出并将更新值写回的需要,将每次迭代的内存操作从两次读和一次写减少到只需一次读)。时间局部性指的就是在这个过程中我反复要访问这个变量,那么这个变量就具有很好的时间局部性。
空间局部性指的是访问一个变量后,后面要访问到这个变量附近的数据。
举个例子,对一个数组求和:
int sum = 0;
for(int i = 0; i < n; ++i)
{
sum += sum + a[i];
}
我们可以看到,sum在每次迭代中都要访问到,有好的时间局部性。而a[i]是数组的某一个元素,访问一次后,再也不访问,因此时间局部性很差,但是之后会访问到后面的元素,数组是顺序存储,因此有好的空间局部性。
那么高速缓存到底是怎样给上述例子提供强有力的支持呢
高速缓存的底层实现
假设内存有M=2^m大的地址空间(实际上并不需要是2的幂),地址如下表示:
高速缓存在中间去s位作组号(S=2s个组),取h位作标记位(H=2h),b位作块大小(B=2^b)。
这样做,虽然高速缓存并没有内存大,但是内存的每一个地址都能缓存到SRAM上,注意,一个地址对应这个地址上一个字节(字节序列角度),高速缓存可不会缓存你一个地址上的一个字节(太小了,一滴水救火?),如果组好确定,那你后面B=2^b个地址是不是都在我一个组?那我高速缓存一个组直接就存B个字节大小的块。打个比方,int a[4]数组,在内存里连续存储16个字节,假如我取b=2,那么我B=2^4=16个字节,那么整个数组就缓存到了高速缓存中,这个时候我如果访问a的第一个元素,如果不在缓存中(不命中),那么我就会从内存取对应的16个字节到高速缓存中,这个时候结合上面的例子去思考空间局部性,是不是有种豁然开朗的感觉。
直接映射高速缓存和组相联高速缓存
在这里简单讲一下直接映射高速缓存和组相联高速缓存,直接映射高速缓存就是一个组里面只有一行。相联高速缓存呢,一个组有k行就是k路组相联高速缓存。全相联高速缓存就是只有一个组。
那么现在,CPU要访问某个数据,对应的要先在高速缓存里找有没有,那么就根据分析物理地址找到组,行,匹配标记位,看有效位,最后根据块偏移找到块中对应数据。
为什么用中间的位来做索引
为了使高速缓存有一个好的使用效率
有关写的问题
如果我们要写一个以及缓存了的字w,在高速缓存里更新了w的副本之后,怎么更新存储器层次结构的低一层呢?
有两种方法:
第一种是直写,就是立即将w的高速缓存块写回到低一层中。缺点就是每次写都会引起总线流量。
第二种是写回,就是说我在高速缓存中更新了副本之后我不马上写回到低一层中,如果我还有访问更新值呢?所以知道我这个w被挤出高速缓存,我再写回到低一层。
编写高速缓存友好的代码
我们熟悉局部性之后,就不难理解怎么样才能让程序尽可能运行得快了。
- 让最常见得情况运行得快
- 尽量减少每个循环内部得缓存不命中数量
后期我会拓展并更新,并相应配图。

浙公网安备 33010602011771号