与程序员相关的CPU缓存知识


与程序员相关的CPU缓存知识文章插图
好久没有写一些微观方面的文章了 , 今天写一篇关于CPU Cache相关的文章 , 这篇文章会讲述一些多核 CPU 的系统架构以及其原理 , 包括对程序性能上的影响 , 以及在进行并发编程的时候需要注意到的一些问题 。 这篇文章我会尽量地写简单和通俗易懂一些 , 主要是讲清楚相关的原理和问题 , 而对于一些细节和延伸阅读我会在文章最好给出相关的资源 。
本文比较长 , 主要分成这么几个部分:基础知识、缓存的命中、缓存的一致性、相关的代码示例和延伸阅读 。
因为无论你写什么样的代码都会交给CPU来执行 , 所以 , 如果你想写出性能比较高的代码 , 这篇文章中的技术还是应该认真学习的 。
基础知识首先 , 我们都知道现在的CPU多核技术 , 都会有几级缓存 , 老的CPU会有两级内存(L1和L2) , 新的CPU会有三级内存(L1 , L2 , L3 ) , 如下图所示:
与程序员相关的CPU缓存知识文章插图
其中:

  • L1缓分成两种 , 一种是指令缓存 , 一种是数据缓存 。 L2缓存和L3缓存不分指令和数据 。
  • L1和L2缓存在第一个CPU核中 , L3则是所有CPU核心共享的内存 。
  • L1、L2、L3的越离CPU近就越小 , 速度也越快 , 越离CPU远 , 速度也越慢 。
再往后面就是内存 , 内存的后面就是硬盘 。 我们来看一些他们的速度:
  • L1 的存取速度: 4 个CPU时钟周期
  • L2 的存取速度: 11 个CPU时钟周期
  • L3 的存取速度: 39 个CPU时钟周期
  • RAM内存的存取速度 :107 个CPU时钟周期
我们可以看到 , L1的速度是RAM的27倍 , 但是L1/L2的大小基本上也就是KB级别的 , L3会是MB级别的 。 例如: Intel Core i7-8700K, 是一个6核的CPU , 每核上的L1是64KB(数据和指令各32KB) , L2 是 256K , L3有12MB(我的苹果电脑是 Intel Core i9-8950HK, 和Core i7-8700K的Cache大小一样) 。
于是我们的数据就从内存向上 , 先到L3 , 再到L2 , 再到L1 , 最后到寄存器进行CPU计算 。 为什么会设计成三层?这里有下面几个方面的考虑:
  • 一个方面是物理速度 , 如果你要更在的容量就需要更多的晶体管 , 除了芯片的体积会变大 , 更重要的是大量的晶体管会导致速度下降 , 因为访问速度和要访问的晶体管的位置成反比 , 也就是当信号路径变长时 , 通信速度会变慢 。 这部分是物理问题 。
  • 另外一个问题是 , 多核技术中 , 数据的状态需要在多个CPU中进行同步 , 并且 , 我们可以看到 , cache和RAM的速度差距太大 , 所以 , 多级不同尺寸的缓存有利于提高整体的性能 。
这个世界永远是平衡的 , 一面变得有多光鲜 , 另一面也会变得有多黑暗 。 建立这么多级的缓存 , 一定就会引入其它的问题 , 这里有两个比较重要的问题 ,
  • 一个是比较简单的缓存的命中率的问题 。
  • 另一个是比较复杂的缓存更新的一致性问题 。
尤其是第二个问题 , 在多核技术下 , 这就很像分布式的系统了 , 要对多个地方进行更新 。
缓存的命中在说明这两个问题之前 。 我们需要要解一个术语 Cache Line 。 缓存基本上来说就是把后面的数据加载到离自己进的地方 , 对于CPU来说 , 它是不会一个字节一个字节的加载的 , 因为这非常没有效率 , 一般来说都是要一块一块的加载的 , 在CPU的缓存技术中 , 这个术语叫“Cache Line”(有的中文编译成“缓存线”) , 一般来说 , 一个主流的CPU的Cache Line 是 64 Bytes(也有的CPU用32Bytes和128Bytes) , 也就是16个32位的整型 。 也就是说 , CPU从内存中捞数据上来的最小数据单位 。
比如:Cache Line是最小单位(64Bytes) , 所以先把Cache分布多个Cache Line , 比如:L1有32KB , 那么 , 32KB/64B = 500 个 Cache Line 。
一方面 , 缓存需要把内存里的数据放到放进来 , 英文叫 CPU Associativity 。 Cache的数据放置的策略决定了内存中的数据块会拷贝到CPU Cache中的哪个位置 , 因为Cache的大小远远小于内存 , 所以 , 需要有一种地址关联的算法 , 能够让内存中的数据可以被映射到cache中来 。 这个有点像内存管理的方法 。
基本上来说 , 我们会有如下的一些方法 。