2多线程教程(二十七)cpu缓存、伪共享
一、CPU缓存结构
查看 cpu 缓存
⚡ root@yihang01 ~ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 1
On-line CPU(s) list: 0
Thread(s) per core: 1
Core(s) per socket: 1
Socket(s): 1
NUMA node(s): 1
Vendor ID: GenuineIntel
CPU family: 6
Model: 142
Model name: Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz
Stepping: 11
CPU MHz: 1992.002
BogoMIPS: 3984.00
Hypervisor vendor: VMware
Virtualization type: full
L1d cache: 32K
L1i cache: 32K
L2 cache: 256K
L3 cache: 8192K
NUMA node0 CPU(s): 0
速度比较
从 cpu 到 | 大约需要的时钟周期 |
---|---|
寄存器 | 1 cycle |
L1 | 3~4 cycle |
L2 | 10~20 cycle |
L3 | 40~45 cycle |
内存 | 120~240 cycle |
寄存器可以理解为在cpu内部,cpu到寄存器的速度是最快的
时钟周期和cpu的主频有关,比如4GHZ的主频,一个时间周期约为0.25ns
为了提高cpu的利用率,我们将内存的数据读到缓存里
因为 CPU 与 内存的速度差异很大,需要靠预读数据至缓存来提升效率。
而缓存以缓存行为单位,每个缓存行对应着一块内存,一般是 64 byte(8 个 long)
缓存的加入会造成数据副本的产生,即同一份数据会缓存在不同核心的缓存行中
CPU 要保证数据的一致性,如果某个 CPU 核心更改了数据,其它 CPU 核心对应的整个缓存行必须失效
更详细的cpu缓存机制可以看博客,讲的非常不错
二、伪共享
前面提到缓存是以缓存行伪单位,每个缓存行对应着一块内存,一般是 64 byte(8 个 long)
但是如果数据的数据量小于64byte的一半即32byte,缓存行就会存储2个数据,两个数据之中有一个数据变化就会造成整个缓存行的失效,这就是伪共享。
以上一届介绍的LongAdder为例,cell为累加单元,LongAdder为了提高效率使用了多个cell。
因为 Cell 是数组形式,在内存中是连续存储的,一个 Cell 为 24 字节(16 字节的对象头和 8 字节的 value),因此缓存行可以存下 2 个的 Cell 对象。这样问题来了:
Core-0 要修改 Cell[0]
Core-1 要修改 Cell[1]
无论谁修改成功,都会导致对方 Core 的缓存行失效,比如 Core-0 中 Cell[0]=6000, Cell[1]=8000 要累加Cell[0]=6001, Cell[1]=8000 ,这时会让 Core-1 的缓存行失效
@sun.misc.Contended 用来解决这个问题,它的原理是在使用此注解的对象或字段的前后各增加 128 字节大小的padding,从而让 CPU 将对象预读至缓存时占用不同的缓存行,这样,不会造成对方缓存行的失效
参考文献: