缓存操作
CPU缓存
内存对于CPU来说还是太慢,进而发展出L1,L2,L3三级缓存
L1缓存最接近CPU,存储容量小但是速度最快
速度参照
- L1缓存1ns
- L2缓存3ns
- L3缓存15ns
- 内存60ns
https://github.com/colin-scott/interactive_latencies
CPU首选缓存操作。如果写操作时,缓存存在就是写命中,否则是写缺失,层层缓存寻找,都没有的情况下去内存加载
CPU缓存通信
L1,L2一般是CPU核专属缓存,L3是核共享缓存
专属缓存相对独立的同时产生了不一致问题,因此缓存间需要一套通信机制
MESI协议规定了缓存状态
- M(Modified): 和内存不一致,已修改
- E(Exclusive):和内存一致,其他核没有
- S(Shared):和内存一致,其他核也有
- I(Invalid):失效
写操作发送RFO(Request For Owner)请求,获得权限后其他缓存副本设为失效
伪共享(false sharing)
多核状态下多个线程操作同一对象,对象内表面看逻辑上没关系的两个字段,其实在执行时可能存在缓存行竞争
CPU以缓存行为单位,即块操作,常见大小是64字节
如果两个独立变量xy碰巧位于同一缓存行,一个CPU修改x,另一CPU修改y,导致两个CPU都加载了同一缓存行
那么任意修改xy都会造成另一CPU对应的缓存行失效,那么就要发送RFO请求进行状态控制,造成了竞争
解决方案
编程时可以刻意定义多余无用的成员变量进行填补,避免特定变量间使用同一缓存行
Java8增加了标签@sun.misc.Contended
,设置JVM参数启用-XX:-RestrictContended
注解使得被标记字段单独占用一个缓存行,虽然浪费空间但是避免竞争提升了效率
应用
Striped64
类使用数组减少竞争,是LongAdder的基类
数组容易连续分配,造成缓存伪共享,因此加上@Contended
标签进行优化
1 | abstract class Striped64 extends Number { |
版权声明
This site by Linest is licensed under a Creative Commons BY-NC-ND 4.0 International License.
由Linest创作并维护的博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证。
本文永久链接:http://linest.github.io/2019/02/03/java-system-cache/