华为大佬科普高速缓存:CPU Cache是什么鬼?

华为大佬科普高速缓存:CPU Cache是什么鬼?

类别:武汉代生儿子保密咨询 日期:2024-04-16 09:06

程序里的循环1和循环2,运行所花费的时间会差多少?你可以先思考几分钟,然后再看我下面的解释。


首先构造了一个64×1024×1024整型数组。

loop1,遍历整个数组,将数组中每一项的值变成原3倍

loop2,每隔16个索引访问一个数组元素,将该项的值变成原3倍
2只访问了1中1/161/16的数组元素,只进行1中1/161/16的乘法计算,那循环2花费的时间应是循环1的1/161/16左右。

但实际loop1在我的电脑上运行需50ms,循环2只需46ms,差在15%内。

和CPUCache有关,内存和硬盘间存在巨大性能差异,对于CPU来说,内存也很慢。就在CPU里嵌入CPUCache(高速缓存)解决这问题。

为什么需要高速缓存?

按摩尔定律,CPU访问速度每18个月便会翻一番,即每年增长60%。内存访问速度虽也在不断增长,却远没这么快,每年只增长7%。
这俩增长速度的差异,使得CPU性能和内存访问性能的差距不断拉大。到今天来看,一次内存的访问,大约需要120个CPUCycle,这也意味着,在今天,CPU和内存的访问速度已经有了120倍的差距。

如果拿我们现实生活来打个比方的话,CPU的速度好比风驰电掣的高铁,每小时350公里,然而,它却只能等着旁边腿脚不太灵便的老太太,也就是内存,以每小时3公里的速度缓慢步行。因为CPU需要执行的指令、需要访问的数据,都在这个速度不到自己1%的内存里。

随时间变迁,CPU和内存间性能差距越来越大

为弥补两者之间的性能差异,能真的用起来CPU的性能提升,而不是让它空转,在现代CPU中引入了高速缓存。

从CPUCache被加入CPU开始,内存中的指令、数据,会被加载到L1-L3Cache,而非直接由CPU访问内存去拿。
95%情况下,CPU只需访问L1-L3Cache,从中读取指令和数据,无需访问内存。这里CPUCache或L1/L3Cache,并非单纯概念上的缓存(如拿内存作为硬盘的缓存),而是指特定的由SRAM组成的物理芯片。

IntelCPU的放大照片。这里面大片的长方形芯片,就是该CPU使用的20MBL3Cache

现代CPU中大量的空间已经被SRAM占据,图中红框部分就是CPUL3Cache芯片

文头提到的运行程序的时间主要花在将对应数据从内存读出来,加载到CPUCache。
CPU从内存中读取数据到CPUCache过程中,是一小块一小块读数据,而非按单个数组元素读取数据。这样一小块一小块的数据,在CPUCache里叫作CacheLine(缓存块)。

日常使用Intel服务器或PC里,CacheLine一般64字节。而在上面的loop2,每隔16个整型数计算一次,16个整型数正好64字节。于是,循环1和循环2,需要把同样数量的CacheLine数据从内存中读取到CPUCache中,最终两个程序花费的时间就差别不大了。

CPU究竟是如何访问CPUCache的,以及CPUCache是如何组织数据,使得CPU可以找到自己想要访问的数据的。

Cache作为“缓存”意思,在很多别的存储设备都会用到。为避免混淆:

表示抽象的“缓存“概念时,用中文“缓存”

CPUCache,用“高速缓存“或英文“Cache”表示

Cache的数据结构和读取过程

CPU进行数据读取时,无论数据是否已存储在Cache,CPU始终会首先访问Cache。只有当CPU在Cache找不到数据,才会访问内存,并将读取到的数据写入Cache。

当时间局部性原理起作用,这最近刚被访问的数据,会很快再次被访问。
而Cache访问速度远快于内存,这样,CPU花在等待内存访问上的时间就大大变短。


这样的访问机制,和开发应用系统时,“使用内存作为硬盘的缓存”逻辑一致。
在各类Benchmark和实际应用场景中,CPUCache命中率通常95%以上。

直接映射Cache(DirectMappedCache)

CPU访问内存数据,是一块块的数据读取。对读取内存中的数据,首先拿到的是数据所在内存块(Block)的地址。

直接映射Cache就是确保任何一个内存块的地址,始终映射到一固定CPUCache地址(CacheLine)。这映射关系,通常用mod运算(求余运算)实现。

如主内存被分成0~31号32个块。共8个缓存块。用户想访问第21号内存块。如果21号内存块内容在缓存块中的话,它一定在5号缓存块(21mod8=5)中。

Cache采用mod的方式,把内存块映射到对应的CPUCache中

通常会把缓存块的数量设置成2的N次方,取模时,可直接取地址低N位,即二进制里面的后几位。比如这里的8个缓存块,就是2的3次方。在对21取模时,可对21的2进制表示10101取地址的低三位101,对应5,就是对应缓存块地址。

取Block地址的低位,就能得到对应CacheLine地址,除了21号内存块,13号、5号等很多内存块数据,都对应5号缓存块。
既然如此,若CPU想读取21号内存块,在读取到5号缓存块时,怎么知道里面数据是不是21号对应数据?
这时,在对应缓存块中,会存储一个组标记(Tag):记录当前缓存块内存储的数据对应的内存块,而缓存块本身的地址表示访问地址的低N位。
案例中21的低3位101,缓存块本身的地址已涵盖对应信息、对应组标记,只需记录21剩余的高2位信息10即可。

除了组标记信息,缓存块还有两个数据:

从主内存中加载来的实际存放的数据

有效位(validbit)
标记对应的缓存块中的数据是否是有效,确保不是机器刚启动时的空数据。
若有效位==0有效位==0,无论其组标记、CacheLine中数据内容,CPU都不会管这些数据,而要直接访问内存,重新加载数据。

CPU读数据时,并非要读取一整个Block,而是读取一个他需要的整数。这样的数据叫CPU里的一个字(Word)。

就用该字在整个Block里的位置决定。这位置叫偏移量(Offset)。一个内存的访问地址,最终包括:

高位代表的组标记

低位代表的索引

在对应DataBlock中定位对应字的位置偏移量

看如下内存地址到CacheLine的关系:


内存地址对应到Cache里的数据结构,则多了一个有效位和对应的数据,由“索引+有效位+组标记+数据”组成。
若内存中的数据已在CPUCache,那一个内存地址的访问,就会经历:

根据内存地址的低位,计算在Cache中的索引

判断有效位,确认Cache中的数据是有效的

对比内存访问地址的高位,和Cache中的组标记,确认Cache中的数据就是我们要访问的内存数据,从CacheLine中读取到对应的数据块(DataBlock)

根据内存地址的Offset位,从DataBlock中,读取希望读取到的字

若2、3两步中,CPU发现,Cache中数据并非想访问的内存地址的数据,则CPU就会访问内存,并把对应BlockData更新到CacheLine,同时更新对应的有效位和组标记的数据。

这就是CPU如何通过直接映射Cache,来定位一个内存访问地址在Cache中的位置了。
除了直接映射Cache,常见缓存放置策略还有全相连Cache(FullyAssociativeCache)、组相连Cache(SetAssociativeCache)。这几种策略的数据结构都是相似的。

一次内存访问,只不过100纳秒。1s内,足有1000万个100纳秒。引入CPUCache之后,我们可以进行的数据访问次数,提升了数十倍,使得各种高频交易成为可能。

总结

很多时候,程序的性能瓶颈,来自使用DRAM芯片的内存访问速度。

根据摩尔定律,自上世纪80年代以来,CPU和内存的性能鸿沟越拉越大。于是,现代CPU的设计者们,直接在CPU中嵌入了使用更高性能的SRAM芯片的Cache,弥补这性能差异。

将内存地址,拆分成“索引+组标记+偏移量”,可将很大内存地址,映射到很小CPUCache地址。而CPUCache带来的毫秒乃至微秒级别的性能差异,又能带来巨大商业利益,十多年前的高频交易行业就是佳话。

搞清楚从内存加载数据到Cache,以及从Cache里读取到想要的数据之后。CPU不仅要读数据,还需要写数据,不能只把数据写Cache就结束了。

CPU要写入数据的时候,怎么既不牺牲性能,又能保证数据的一致性呢?请等系列文章下一篇。


参考资料