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

华为格式化cache什么意思(华为大佬科普高速缓存)(1)

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

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

为何有15%差异?

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

为什么需要高速缓存?

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

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

  • 随时间变迁,CPU和内存间性能差距越来越大为弥补两者之间的性能差异,能真的用起来CPU的性能提升,而不是让它空转,在现代CPU中引入了高速缓存。

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

Intel CPU的放大照片。这里面大片的长方形芯片,就是该CPU使用的20MB L3 Cache

  • 现代CPU中大量的空间已经被SRAM占据,图中红框部分就是CPU L3 Cache芯片文头提到的运行程序的时间主要花在将对应数据从内存读出来,加载到CPU Cache。CPU从内存中读取数据到CPU Cache过程中,是一小块一小块读数据,而非按单个数组元素读取数据。这样一小块一小块的数据,在CPU Cache里叫作Cache Line(缓存块)。

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

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

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

  • 表示抽象的“缓存“概念时,用中文“缓存”
  • CPU Cache,用“高速缓存“或英文“Cache”表示
Cache的数据结构和读取过程

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

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

华为格式化cache什么意思(华为大佬科普高速缓存)(2)

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

CPU如何知道要访问的内存数据,存储在Cache哪个位置?

直接映射Cache(Direct Mapped Cache)

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

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

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

  • Cache采用mod的方式,把内存块映射到对应的CPU Cache中通常会把缓存块的数量设置成2的N次方,取模时,可直接取地址低N位,即二进制里面的后几位。比如这里的8个缓存块,就是2的3次方。在对21取模时,可对21的2进制表示10101取地址的低三位101,对应5,就是对应缓存块地址。取Block地址的低位,就能得到对应Cache Line地址,除了21号内存块,13号、5号等很多内存块数据,都对应5号缓存块。既然如此,若CPU想读取21号内存块,在读取到5号缓存块时,怎么知道里面数据是不是21号对应数据?这时,在对应缓存块中,会存储一个组标记(Tag):记录当前缓存块内存储的数据对应的内存块,而缓存块本身的地址表示访问地址的低N位。案例中21的低3位101,缓存块本身的地址已涵盖对应信息、对应组标记,只需记录21剩余的高2位信息10即可。

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

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

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

是哪个字呢?

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

  • 高位代表的组标记
  • 低位代表的索引
  • 在对应Data Block中定位对应字的位置偏移量

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

华为格式化cache什么意思(华为大佬科普高速缓存)(3)

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

  1. 根据内存地址的低位,计算在Cache中的索引
  2. 判断有效位,确认Cache中的数据是有效的
  3. 对比内存访问地址的高位,和Cache中的组标记,确认Cache中的数据就是我们要访问的内存数据,从Cache Line中读取到对应的数据块(Data Block)
  4. 根据内存地址的Offset位,从Data Block中,读取希望读取到的字

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

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

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

总结

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

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

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

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

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

,