1.概述
转载:https://mp.weixin.qq.com/s/jag8rWNFoKQE-duHRV-0xQ
我有写过类似的问题:【java】java 如何证明linux缓存行确实存在
1.1 什么是伪共享?
CPU的缓存是以缓存行(cache line)为单位进行缓存的,当多个线程修改相互独立的变量,而这些变量又处于同一个缓存行时就会影响彼此的性能。这就是伪共享
现代计算机计算模型:
CPU执行速度比内存速度快好几个数量级,为了提高执行效率,现代计算机模型演变出CPU、缓存(L1,L2,L3),内存的模型。
CPU执行运算时,如先从L1缓存查询数据,找不到再去L2缓存找,依次类推,直到在内存获取到数据。
为了避免频繁从内存获取数据,聪明的科学家设计出缓存行,缓存行大小为64字节。
也正是因为缓存行的存在 ,就导致了伪共享问题,如图所示:
假设数据a、b被加载到同一个缓存行。
-
当线程1修改了a的值,这时候CPU1就会通知其他CPU核,当前缓存行(Cache line)已经失效。
-
这时候,如果线程2发起修改b,因为缓存行已经失效了,所以「core2 这时会重新从主内存中读取该 Cache line 数据」。读完后,因为它要修改b的值,那么CPU2就通知其他CPU核,当前缓存行(Cache line)又已经失效。
-
酱紫,如果同一个Cache line的内容被多个线程读写,就很容易产生相互竞争,
频繁回写主内存,会大大降低性能。
1.2 如何解决伪共享问题
既然伪共享是因为相互独立的变量存储到相同的Cache line导致的,一个缓存行大小是64字节
。那么,我们就可以使用空间换时间 的方法,即数据填充的方式 ,把独立的变量分散到不同的Cache line~
/**
* 更多干货内容,关注公众号:芋道源码
*/
public class FalseShareTest {
public static void main(String[] args) throws InterruptedException {
Rectangle rectangle = new Rectangle();
long beginTime = System.currentTimeMillis();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
rectangle.a = rectangle.a + 1;
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100000000; i++) {
rectangle.b = rectangle.b + 1;
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("执行时间" + (System.currentTimeMillis() - beginTime));
}
}
class Rectangle {
volatile long a;
volatile long b;
}
//运行结果:
执行时间2815
一个long类型是8字节,我们在变量a和b之间不上7个long类型变量呢,输出结果是啥呢?如下:
class Rectangle {
volatile long a;
long a1,a2,a3,a4,a5,a6,a7;
volatile long b;
}
//运行结果
执行时间1113
可以发现利用填充数据的方式,让读写的变量分割到不同缓存行,可以很好挺高性能~