CPU缓存与一致性
在CPU高速缓存与内存屏障的介绍中,CPU在对数据进行读取的时候遵循缓存一致性来解决高速缓存的数据不一致问题,现简述如下:
伪共享定义以及产生原因
// FalseShared.java
public class FalseShared {
// 存储连续数据,同一行
private final static int[][] arr2continuous = new int[1024][1024];
// 存储不连续数据,同一列
private final static int[][] arr2notcontinuos = new int[1024][1024];
static {
for (int index = 0; index < 1024; index ++){
arr2continuous[0][index] = index * 2 + 1;
arr2notcontinuos[index][0] = index * 2 + 1;
}
}
private static void readByContinuous(){
long start = System.currentTimeMillis();
int len = arr2continuous.length;
for (int row = 0; row < len; row ++){
for (int col = 0; col < len; col ++){
// 读取1数据的时候会先从内存加载并把与1连续的数据也一起加载到缓存,下次读取3的时候是从缓存读取
long temp = arr2continuous[row][col];
}
}
long end = System.currentTimeMillis();
System.out.println("read arr by continuous with time : "+ (end - start));
}
private static void readByNotContinuous(){
long start = System.currentTimeMillis();
int len = arr2notcontinuos.length;
for (int row = 0; row < len; row ++){
for (int col = 0; col < len; col ++){
// 读取1的时候并没有发现有连续数据的,因此只会copy数据1到缓存,也就是下次读取3的时候还要从
// 主内存中读取
long temp = arr2notcontinuos[col][row];
}
}
long end = System.currentTimeMillis();
System.out.println("read arr by not continuous with time : "+ (end - start));
}
public static void main(String[] args) throws Exception {
new Thread(){
@Override
public void run() {
readByContinuous();
}
}.start();
new Thread(){
@Override
public void run() {
readByNotContinuous();
}
}.start();
}
}
## case 1
read arr by continuous with time : 12
read arr by not continuous with time : 24
## case 2
read arr by continuous with time : 12
read arr by not continuous with time : 19
// ...
// shared.java
int x = 10;
int y = 9;
// write thread
run(){
x = 11;
}
// read thread
run(){
TimeUnit.SECONDS.sleep(1L);
int x1 = x;
int temp = y;
}
使用
@sun.misc.Contended
解决伪共享的问题
// Thread.java
// The following three initially uninitialized fields are exclusively
// managed by class java.util.concurrent.ThreadLocalRandom. These
// fields are used to build the high-performance PRNGs in the
// concurrent code, and we can not risk accidental false sharing.
// Hence, the fields are isolated with @Contended.
/** The current seed for a ThreadLocalRandom */
@sun.misc.Contended("tlr")
long threadLocalRandomSeed;
/** Probe hash value; nonzero if threadLocalRandomSeed initialized */
@sun.misc.Contended("tlr")
int threadLocalRandomProbe;
/** Secondary seed isolated from public ThreadLocalRandom sequence */
@sun.misc.Contended("tlr")
int threadLocalRandomSecondarySeed;
@sun.misc.Contended
public class ForkJoinPool extends AbstractExecutorService {
// 表示这个类下的属性内存地址在cache line都具备连续性
}
// source.java
public static class ContendedTest1 {
@Contended
private Object contendedField1;
private Object plainField1;
private Object plainField2;
private Object plainField3;
private Object plainField4;
}
// 输出
TestContended$ContendedTest1: field layout
@ 12 --- instance fields start ---
@ 12 "plainField1" Ljava.lang.Object;
@ 16 "plainField2" Ljava.lang.Object;
@ 20 "plainField3" Ljava.lang.Object;
@ 24 "plainField4" Ljava.lang.Object;
@156 "contendedField1" Ljava.lang.Object; (contended, group = 0)
@288 --- instance fields end ---
@288 --- instance ends ---
// 结果: 将contendedField1与其他字段分配的内存地址区分开,没有放在同一个cache line中(jdk默认cache line是128bit)
@Contended
-XX:-RestrictContended
-XX:-RestrictContended -XX:ContendedPaddingWidth=256
设置生效参考文档
https://dzone.com/articles/what-false-sharing-is-and-how-jvm-prevents-it
http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html
感谢花时间阅读,如果有用欢迎转发或者点个好看,谢谢!