理解CPU Cache


示例代码

int[] arr = new int[64 * 1024 * 1024];
 
 
// 循环 1
for (int i = 0; i < arr.length; i++) arr[i] *= 3;
 
 
// 循环 2
for (int i = 0; i < arr.length; i += 16) arr[i] *= 3

循环1执行了50ms,循环2执行了46ms,两个循环差值在15%之内。这是因为CPU Cache的原因。

为什么需要高速缓存

一次内存的访问,大于需要120个CPU Cycle,意味着CPU和内存的访问速度有了120倍的差距。类比生活中350公里/h的高铁和3公里/h的老太太。
在这里插入图片描述
为了弥补两者之间的差异,引入了高速缓存。

  • CPU Cache被加入到CPU中之后,内存中的指令、数据会被加载到L1-L3的Cache中,不再直接由CPU访问内存去拿。
  • 95%的情况下,CPU只需要访问L1-L3 Cache,从里面读取指令和数据,无需访问内存,
  • CPU Cache是指特定的由SRAM组成的物理芯片

示例代码中,运行程序的时间主要花在了将对应的数据从内存中读取出来,加载到CPU Cache里。

CPU从内存读取到CPU Cache的过程中,是一小块一小块来读取数据的,不是按照单个数组元素来读取数据的。

一小块一小块的数据在CPU Cache里,叫做Cache Line(缓存块)。

日常使用的Intel服务器中,Cache Line的大小通常是64字节。示例代码中循环2每隔16个整型计算一次,16个整型正好是64个字节。所以循环1和循环2,需要把同样数据量的Cache Line数据从内存读取到CPU Cache中,最终两个程序花费的时间就差别不大了。

Cache的数据结构和读取过程

无论数据是否已经存储在Cache中,CPU始终首先访问Cache。

只有当CPU在Cache中找不到数据的时候,才会去访问内存,并将读取到的数据写入Cache之中,根据时间局部性,很快会再被访问。

在这里插入图片描述
这样的访问机制中,和开发应用系统时,使用内存作为硬盘的缓存的逻辑是一致的。

各类基准测试和实际场景中,CPU Cache的命中率能达到95%以上。

直接映射Cache

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

Cache的数据结构

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

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

举例如下:

比如主内存被分成0-31号这样32个块,一共有8个缓存块,用户想要访问第21号内存块,如果21号内存块在缓存块中的话,一定在5号缓存块(21%8=5)中。
在这里插入图片描述
实际计算中,有一个小小的技巧,通常我们会把缓存块的数量设置成 2 的 N 次方。这样在计算取模的时候,可以直接取地址的低 N 位,也就是二进制里面的后几位。比如这里的 8 个缓存块,就是 2 的 3 次方。那么,在对 21 取模的时候,可以对 21 的 2 进制表示 10101 取地址的低三位,也就是 101,对应的 5,就是对应的缓存块地址。
在这里插入图片描述

Cache的访问逻辑

取Block地址的低位,得到对应的Cache Line地址,除了21号内存块外,13号、5号等很多内存块的数据,都对应着5号缓存块,如何判断5号缓存块里的数据对应的是内存块的哪一个呢?组标记。

  • 组标记:对应的缓存块中,会存储一个组标记,组标记会记录当前缓存块对应的内存块,缓存块本身的地址表示访问地址的低N位,比如21的低3位101,缓存块本身的地址已经覆盖了对应的信息,对应的组标记,只需要记录21剩余的高2位的信息,也就是10就可以了。

  • 有效位:用来标记对应的缓冲块中的数据是否有效,确保不是机器刚刚启动时候的空数据,有效位是0,无论其中的组标记和Cache Line里的数据内容是什么,CPU不会管这些数据直接去访问内存,重新加载数据。

  • 数据:从主内存加载进来的实际存放的数据

  • CPU在读取数据时候,不是读取整个Block,而是读取一个他需要的整数,这样的数据叫做CPU的一个字,这个字在整个Block里面的位置叫做偏移量。

一个内存地址访问地址,最终包括高位代表的组标记、低位代表的索引,以及在对应的Data Block中定位对应字的位置偏移量。

在这里插入图片描述

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

  • 根据内存的低位,计算在Cache中的索引
  • 判断有效位,确认Cache的数据有效
  • 对比内存访问的高位和Cache里的组标记,确认Cache中的数据就是要访问的内存数据,从Cache Line读取到对应的数据块
  • 根据内存低的孩子的Offset位,从Block Data更新到Cache Line中,同时更新对应的有效位和组标记对应的数据。

如果在 2、3 这两个步骤中,CPU 发现,Cache 中的数据并不是要访问的内存地址的数据,那 CPU 就会访问内存,并把对应的 Block Data 更新到 Cache Line 中,同时更新对应的有效位和组标记的数据。

除了直接映射 Cache 之外,我们常见的缓存放置策略还有全相连 Cache(Fully Associative Cache)、组相连 Cache(Set Associative Cache)。这几种策略的数据结构都是相似的,理解了最简单的直接映射 Cache,其他的策略你很容易就能理解了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值