CPU 取数逻辑

CPU 执行运算的时候,经 L1 > L2 > L3 查找所需的数据,如果这些缓存中都没有,只能主内存(物理内存)拿

Memory ~60-80ns

QPI 总线传输 (between sockets, not drawn) ~20ns

L3 cache ~40-45 cycles, ~15ns

L2 cache ~10 cycles ~3ns

L1 cache ~3-4 cycles ~1ns

寄存器 1 cycle

尽可能地让缓存命中而不是跨越地去取数据

Cache Line

数据在内存中以 page 为单位存在;在cache 里以 cache line 为单位

2 的整数幂个连续字节,一般 32-256,最常见 64 字节

java 一个 long 8 字节,一个 cache line 可放 8 个 long 变量

访问 long[] 第 0 个元素时,数组的 0-7 元素都被会加载到 cache 里,即便此后并不需要访问 1-7 的元素

恰好访问完 0 元素就像访问 1 的话(遍历数组),赚到了。相反要访问的数据并不在连续的内存中时,将无法从 cache line 这种整体加载中得到好处

这种整体加载在某些情况下它反倒会起到副作用。因为同一 cache line 上的数据是绑在一根绳上的蚂蚱

False Sharing

volatile long head;
volatile long tail;

head 和 tail 会被加载到一个 cache line 中,但多线程的情况

head 被 Thread A 写入,tail 被 Thread B 读取。A 和 B 运行于不同 CPU 核心上

假设 head 被 A 更新了,cache 和主存中 head 的值都会被更改。导致所有 head 所在的 cache line 都失效,包括 B 使用的 cache line——即便它其实并不关心 head 的值

而 B 此时如果需要读取 tail,就必须重新从主存中加载 head 和 tail 到一个 cache line,即便 tail 没有被更改过

甚的情况是如果 B 也是写入操作,那么 A 和 B 任意一方的写操作都会让对方的 cache line 整体失效从而导致重新从主存加载

这就是传说中的 False Sharing.

问题在于这一切都在幕后悄然发生,没有任何编译告警提示你说你这段代码并发性能会非常低下