隐藏性能杀手之 '伪共享'

随着CPU工艺的发展,目前的高端CPU已经存在几十核心百多个线程,并为CPU设计出了一二三级缓存。CPU的核心有了这些缓存就可以加快数据的处理,从而减少访问内存的频率,这样CPU的计算性能可以进一步得到提高。

CPU的缓存结构以及内存硬盘:

众所周知CPU去访问一次内存所需要的开销是非常之大的,想要获取一次磁盘上的数据更是需要等待较长的时间,虽然目前已经有很多解决方案如 mmap 技术来缓解这样的情况,但总体来说CPU的计算性能是整个计算机结构中的天花板,其他硬件从数据传输速度层面对比起来就显得拖后腿,那么我们来看一下具体CPU访问每个硬件的延迟:

存储器 存储介质 介质成本(美元) 随机访问延迟
L1 cache SRAM 7 1ns
L2 cache SRAM 7 4ns
Memory DRAM 0.015 100ns
Disk SSD(NAND) 0.0004 150us
Disk HHD 0.00004 10ms

可以得出外部存储设备容量越大成本越小,存储数据更多,但访问速度更慢,访问速度越快的设备造价更高,并且可以看到, CPU 访问 L1 Cache 速度比访问内存快 100 倍,这就是为什么 CPU 里会有 L1~L3 Cache 的原因,目的就是把 Cache 作为 CPU 与内存之间的缓存层,以减少对内存的访问频率。

CPU 从内存中读取数据到 Cache 的时候,并不是一个字节一个字节读取,而是一块一块的方式来读取数据的,这一块一块的数据被称为 CPU Line(缓存行),所以 CPU Line 是 CPU 从内存读取数据到 Cache 的单位

至于 CPU Line 大小,在 Linux 系统可以用下面的命令查看。

$ cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_ltne_size

一般主流的CPU都将会是64字节的 CPU Line,但苹果研发的M1CPU,其 CPU Line已经是128字节。

我们知道了内存中的数据是以一块CPU Line 缓存行进行拷贝到CPU Cache中的,那么接下来,我们就来介绍一下 Cache 伪共享是什么?又如何避免这个问题?

假设有一个双核心的 CPU,这两个 CPU 核心并行运行着两个不同的线程,它们同时从内存中读取两个不同的数据,分别是类型为 long 的变量 A 和 B (8字节),及CPU1获取想要获取A数据进行修改,CPU2想要获取B数据进行修改,但这个两个数据的地址在物理内存(DRAM)上是连续的,如果 Cahce Line 的大小是 64 字节,并且变量 A 在 Cahce Line 的开头位置,那么B将会在A的地址后面,那么这两个数据是位于同一个 Cache Line 中,又因为 CPU Line 是 CPU 从内存读取数据到 Cache 的单位,所以这两个数据会被同时读入到了两个 CPU 核心中各自 Cache 中。

我们来思考一个问题,如果这两个不同核心的线程分别修改A,B的数据,比如 CPU1 号核心的线程只修改了变量 A,或CPU2核心的线程只修改了变量 B,会发生什么呢?

此时就会产生两边核心各自的 CPU Line 中数据不一致,这很有可能会影响到最终的计算结果,为了保障两边的 CPU Line 数据一致,于是出现了多核缓存一致的 MESI 协议。

为了准守 MESI,多核心直接不得不通知其他核心,他持有的 CPU Line 已经过期被修改过,需要重新去内存中获取一遍,从而导致性能有损耗。

后续继续补充。。。。。


随心笔记

hi 欢迎留言,共同探讨IT技术~