0%

Cache Line

缓存行 Cache Line CPU 高速缓存中分配的最小存储单位。CPU 缓存分为 Cache L1 L2 L3 一级 二级 三级 缓存,查看本机缓存
pom.xml 引入

<dependency>
<groupId>fr.ujm.tse.lt2c.satin</groupId>
<artifactId>cachesize</artifactId>
<version>0.2.1</version>
</dependency>

不同处理器的缓存行 L1 L2 L3 高速缓存行字节宽不一样 有 32 字节和 64 字节

CacheInfo info = CacheInfo.getInstance();
CacheLevelInfo cacheLevelInfo = info.getCacheInformation(CacheLevel.L1, CacheType.INSTRUCTION_CACHE);
System.out.println("L1 Cache info:" + cacheLevelInfo.toString());

缓存行在内存中加载的地址是连续的 假如缓存行是 64 字节 有两个变量 aba + b 不足 64 字节,那么会在同一个缓存行造成伪共享,处理器修改 a 的时候,其他处理器读取 b 的时候该缓存行是失效的( MESI 协议),其他处理器不得不重新从系统内存中加载。

处理伪共享 可以采取 padding 方式 ,当字节不足缓存行大小时进行填满 和 Java8@sum.misc.Contended 使两个变量 在不同缓存行中

@sun.misc.Contended static final class CounterCell {
volatile long value;
CounterCell(long x) { value = x; }
}

ConcurrentHashMapCounterCell 上加了 @Contended 注解 解决了 ++ 操作产生伪共享

看下 padding 实现 , 使 long 变量进行填充

abstract class SingleProducerSequencerPad extends RingBufferProducer
{
protected long p1, p2, p3, p4, p5, p6, p7; // 4*7 = 28 字节
}
abstract class SingleProducerSequencerFields extends SingleProducerSequencerPad
{

/** Set to -1 as sequence starting point */
protected long nextValue = RingBuffer.Sequence.INITIAL_VALUE; // 28 + 4 = 32 字节
protected long cachedValue = RingBuffer.Sequence.INITIAL_VALUE; // 32 + 4 = 36 字节
}
final class SingleProducerSequencer extends SingleProducerSequencerFields {
protected long p1, p2, p3, p4, p5, p6, p7; // 36 + 28 = 64 字节
}

volatile 也会使处理器缓存回写到内存并且导致其他内存缓存无效,在 64 字节宽的处理器 早期JDK版本采取了追加字节的方式来进行性能优化