起因只是因为面试时被问到的一个问题:什么是CPU伪共享?

CPU缓存

让我们先从CPU缓存讲起。

CPU Cache是计算机为了解决CPU和主存之间的速度差异,引入 的一种存储介质。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。

缓存的有效实际上是依托于程序的时空局部性原理。

使用场景上,当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。

CPU缓存架构

CPU与主存之间通常有三级缓存,缓存靠近CPU缓存的速率就越快,同时容量也越小。CPU获取数据时,它先去 L1 查找所需的数据,再去 L2,然后是 L3,最后如果这些缓存中都没有,所需的数据就要去主内存拿。

608ba4a2f346fb7dd394484a.png 其中:

  • L1 紧靠着在使用它的 CPU 内核。L1,L2只能被一个单独的 CPU 核使用。
  • L3 能够被单个插槽上的所有 CPU 核共享。

CPU缓存块

缓存实际上是以缓存块(或者称之为缓存行)为最小存取单位进行存取的,通常是64个字节,它有效引用了主存中的一块地址(64字节)。

缓存的更新一次性至少更新一个缓存块。

这样加载缓存的好处是,如果我们需要的数据正好彼此相邻,那么就不需要再次更新缓存;

坏处在于可能会导致CPU的伪共享问题。

CPU伪共享

假设这么一个场景:

  • 首先,我们有个 long 类型的变量 a,它不是数组的一部分,而是一个单独的变量,并且还有另外一个 long 类型的变量 b 紧挨着它,那么当加载 a 的时候将免费加载 b。
  • 这个时候,一个 CPU 核心的线程在对 a 进行修改,另一个 CPU 核心的线程却在对 b 进行读取。
  • 当前者修改 a 时,会把 a 和 b 同时加载到前者核心的缓存行中,更新完 a 后其它所有包含 a 的缓存行都将失效,因为其它缓存中的 a 不是最新值了。
  • 而当后者读取 b 时,发现这个缓存行已经失效了,需要从主内存中重新加载。

我们的缓存都是以缓存行作为一个单位来处理的,所以失效 a 的缓存的同时,也会把 b 失效,反之亦然。

https://pic3.zhimg.com/80/v2-32672c4b2b7fc48437fc951c27497bee_1440w.jpg

这样就出现了问题:

b 和 a 完全不相干,每次却要因为 a 的更新需要从主内存重新读取,它被缓存未命中给拖慢了。

CPU的伪共享当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享

避免CPU伪共享

参考

杂谈:什么是CPU伪共享

wikipedia