与程序员相关的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中来 。这个有点像内存管理的方法 。
基本上来说,我们会有如下的一些方法 。