前言
高速缓存,即Cache,是位于CPU与主存之间的一种容量较小但速度很快的存储器,用于解决CPU处理速率和主存访问速率差异过大的问题。CPU将内存中的数据读到高速缓存时,会根据局部性原理,除了读取本次要访问的数据,还会预取本次数据的周边数据到Cache里面,如果CPU后续要读取的数据已经在高速缓存中,那么就会有效提升性能。
存储器层次结构
现代计算机系统基本都采用了层次性的存储结构。在这个层次结构中,存储设备的访问速度越来越慢、容量越来越大,并且单位成本也越来越低。存储层次结构的主要思想就是利用上一级的存储器作为下一层存储器的缓存。
局部性原理
计算机程序运行遵循局部性原则。局部性原理是指程序在执行时呈现出局部性规律,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。局部性原理分为时间局部性原理和空间局部性原理:
- 时间局部性原理(Temporal locality):如果某个数据项被访问,那么在不久的将来它可能再次被访问。
- 空间局部性原理(Spatial locality):如果某个数据项被访问,那么与其地址相邻的数据项可能很快也会被访问。
具有良好局部性的程序比差的程序更多的倾向于从存储器层次结构较高层次处访问数据,因此运行的更快。
高速缓存的结构
高速缓存通常被组织成一个有多个高速缓存组的数组,每个高速缓存组包含若干个高速缓存行。如下图是高速缓存的基本结构:
高速缓存结构的基本组织如下:
- 高速缓存行:高速缓存中的最小访问单元,用于缓存内存块数据;
- 缓存组:由相同索引域的高速缓存行组成,同一内存块可以加载到缓存组的任意行中;
- 路:高速缓存组中包含的高速缓存行数,用于组相联的高速缓存中。
处理器通常使用特定的地址编码访问高速缓存,地址包含标记、组索引、偏移量三个部分:
- 标记(Tag):高速缓存地址编码的一部分,通常是高速缓存地址的高位部分,用于判断高速缓存行缓存的数据的地址是否和处理器寻址地址一致;
- 组索引:高速缓存地址编码的一部分,用于索引和查找地址在高速缓存中的哪一组;
- 偏移量:高速缓存行中的偏移量,处理器可以按字(word)或者字节(Byte)来寻址高速缓存行的内容。
高速缓存的映射方式
由于高速缓存的容量远小于主存,因此需要一种映射算法完成主存到高速缓存的映射,以及确定高速缓存行存储的是哪一块内存数据。映射方式的选择决定了高速缓存的组织结构,通常采用三种映射算法:
- 直接映射
- 组相联映射
- 全相联映射
直接映射
直接映射是最简单的映射技术,只允许将主存中的每个块映射到一个固定可用的缓存行中,这种映射方式下每个高速缓存组只有一个高速缓存行,称为直接映射高速缓存。
直接映射寻址
组相联映射
为了解决直接映射高速缓存中的高速缓存颠簸问题,组相联的高速缓存结构在现代处理器中得到广泛应用。组相联高速缓存每个组内保存有多于一个的高速缓存行,这样就支持同一内存块可以映射到特定缓存组的任意行中。
四路组相联寻址
全相联映射
全相联映射允许每一个内存块装入高速缓存中的任意行,称为全相联高速缓存。全相联高速缓存只包含一个缓存组,并由所有高速缓存行组成。
全相联映射寻址
高速缓存的更新策略
高速缓存更新策略是指当发生缓存命中时,写操作应该如何更新数据。缓存更新策略分成两种:写直通和回写:
- 写直通(write through):当处理器执行存储指令并在缓存命中时,我们更新缓存中的数据并且更新主存中的数据。缓存和主存的数据始终保持一致;
- 写回(write back):当处理器执行存储指令并在缓存命中时,我们只更新缓存中的数据,并且每个缓存行中会有一个bit位记录数据是否被修改过,称之为dirty bit。我们会将dirty bit置位。主存中的数据只会在缓存行被替换或者显示的clean操作时更新。因此,主存中的数据可能是未修改的数据,而修改的数据躺在cache中。cache和主存的数据可能不一致。
相关参考
- 《奔跑吧,Linux内核》
- 《深入理解计算机系统》
- 《计算机组成与体系结构——性能设计》