在阅读Netty源码的时候看到了缓存行优化,之前也有看过类似的优化但一直不是很明白缓存行优化是干什么,今天就将缓存行优化作一个解析。

解析

cpu缓存

由于CPU的速度远远大于内存速度,所以CPU设计者们就给CPU加上了缓存(CPU Cache)。 以免运算被内存速度拖累。(就像我们写代码把共享数据做Cache不想被DB存取速度拖累一样),CPU Cache分成了三个级别:L1,L2,L3。级别越小越接近CPU, 所以速度也更快, 同时也代表着容量越小。
CPU获取数据回依次从L1,L2,L3中查找,如果都找不到则会直接向内存查找。

缓存行

由于共享变量在CPU缓存中的存储是以缓存行为单位,一个缓存行可以存储多个变量(存满当前缓存行的字节数)。

Cache Line可以简单的理解为CPU Cache中的最小缓存单位,今天的CPU不再是按字节访问内存,而是以64字节为单位的块(chunk)拿取,称为一个缓存行(cache line)。当你读一个特定的内存地址,整个缓存行将从主存换入缓存,并且访问同一个缓存行内的其它值的开销是很小的。

什么是伪共享

CPU缓存系统中是以缓存行(cache line)为单位存储的。目前主流的CPU Cache的Cache Line大小都是64Bytes。在多线程情况下,如果需要修改“共享同一个缓存行的变量”,就会无意中影响彼此的性能,这就是伪共享(False Sharing)。

  • 假设在多线程情况下,x,y两个共享变量在同一个缓存行中,核a修改变量x,会导致核b,核c中的x变量和y变量同时失效。
  • 此时对于在核a上运行的线程,仅仅只是修改了了变量x,却导致同一个缓存行中的所有变量都无效,需要重新刷缓存(并不一定代表每次都要从内存中重新载入,也有可能是从其他Cache中导入数据,具体的实现要看各个芯片厂商的实现了)。
  • 假设此时在核b上运行的线程,正好想要修改变量Y,那么就会出现相互竞争,相互失效的情况,这就是伪共享啦。

所以缓存行优化解决的就是伪共享这个问题。

Java对于伪共享的传统解决方案

通过增加变量加到64字节。

1
2
3
4
5
6
public final static class VolatileLong {

public volatile long value = 0L;

public long p1, p2, p3, p4, p5, p6;
}

Java8中的解决方案

Java8中已经提供了官方的解决方案,Java8中新增了一个注解:@sun.misc.Contended。加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在jvm启动时设置-XX:-RestrictContended才会生效。

1
2
3
4
5
6
7
8
@sun.misc.Contended

public final static class VolatileLong {

public volatile long value = 0L;

//public long p1, p2, p3, p4, p5, p6;
}