啥是 CPU 缓存?又如何提高缓存命中率呢?
1 什么是CPU缓存
1.1 CPU缓存的来历
CPU缓存的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。所以,为了解决CPU运算速度与内存读写速度不匹配的矛盾,就出现了CPU缓存。
1.2 CPU缓存的概念
CPU缓存是位于CPU与内存之间的临时数据交换器,它的容量比内存小的多但是交换速度却比内存要快得多。为了简化与内存之间的通信,高速缓存控制器是针对数据块,而不是字节进行操作的。高速缓存其实就是一组称之为缓存行(Cache Line)的固定大小的数据块组成的,典型的一行是64
字节。
1.3 CPU缓存的意义
CPU往往需要重复处理相同的数据、重复执行相同的指令,如果这部分数据、指令CPU能在CPU缓存中找到,CPU就不需要从内存或硬盘中再读取数据、指令,从而减少了整机的响应时间。所以,缓存的意义满足以下两种局部性原理:
时间局部性(Temporal Locality):如果一个信息项正在被访问,那么在近期它很可能还会被再次访问。
空间局部性(Spatial Locality):如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。
任何代码的执行都依赖 CPU,通常,使用好 CPU 是操作系统内核的工作。然而,当我们编写计算密集型的程序时,CPU 的执行效率就开始变得至关重要。如果运算时需要的输入数据是从 CPU 缓存,而不是内存中读取时,运算速度就会快很多。所以,了解 CPU 缓存对性能的影响,便能够更有效地编写我们的代码,优化程序性能。
2 CPU的三级缓存
CPU 缓存离 CPU 核心更近,由于电子信号传输是需要时间的,所以离 CPU 核心越近,缓存的读写速度就越快。但 CPU 的空间很狭小,离 CPU 越近缓存大小受到的限制也越大。所以,综合硬件布局、性能等因素,CPU 缓存通常分为大小不等的三级缓存:L1
,L2
,L3
。级别越小越接近CPU,所以速度也更快,同时也代表着容量越小。
L1 是最接近CPU的, 它容量最小(例如:32K
),速度最快,每个核上都有一个 L1 缓存,L1 缓存每个核上其实有两个 L1 缓存, 一个用于存数据的 L1d Cache(Data Cache),一个用于存指令的 L1i Cache(Instruction Cache)。
L2 缓存 更大一些(例如:256K
),速度要慢一些, 一般情况下每个核上都有一个独立的L2 缓存;
L3 缓存是三级缓存中最大的一级,同时也是最慢的一级, 在同一个CPU插槽之间的核共享一个 L3 缓存。如下这是我的开发机的CPU缓存大小情况。
程序执行时,会先将内存中的数据载入到共享的三级缓存中,再进入每颗核心独有的二级缓存,最后进入最快的一级缓存,之后才会被 CPU 使用,就像下面这张图。
如果 CPU 所要操作的数据在缓存中,则直接读取,这称为缓存命中。命中缓存会带来很大的性能提升,因此,我们的代码优化目标是提升 CPU 缓存的命中率。
2 提升代码缓存命中率
2.1 GO基准测试
基准测试性能结果
通过上边的GO语言基准测试代码,前者顺序遍历与后者非连续遍历,性能差距20倍之多。
2.2 PHP基准测试
由于PHP数组容器与GO等其他语言存在差异性,性能差距不会很大。
如果用顺序访问数组元素,因此访问arr[0][0]时,缓存已经把紧随其后的 3 个元素也载入了,CPU 通过快速的缓存来读取后续 3 个元素。如果用非连续来访问,此时内存是跳跃访问的,如果 N 的数值很大,那么操作arr[j][i]时,是没有办法把 arr[j+1][i]也读入缓存的。
3 结论
在做密集计算场景时,CPU缓存对程序有着不小的影响,无论我们使用的是何种语言,这一结论都有效。CPU 缓存分为数据缓存与指令缓存,对于数据缓存,我们应在循环体中尽量操作同一块内存上的数据,所以顺序地操作连续内存数据时也有性能提升。
在日常业务迭代中,应该有意识的注重这些更底层、细节的优化,能为我们节约出客观的硬件成本。在高性能场景中,我们所写的代码就是在极限压榨硬件性能、最大化利用系统本身的缓存资源,如如何实现百万主机的心跳服务,需要我们去仔细思考,充分压榨硬件资源优势。当然我们也可以通过阅读Swoole的代码,来了解Swoole是如何实现心跳包管理的,因为是更基础的底层服务,所以Swoole在设计之初就已充分的考虑到了各种场景、以及所面临的性能问题。
评论