起因
CPU与物理内存之间, 还有一个高速缓存cache, 从cache读取数据远快于从内存中读取, 如下图
False Sharing
, 字面意思假的共享
, 程序以为可以利用CPU缓存提高性能, 但是实际上事与愿违, 由于某些因素, 导致并未达到预期.
假设内存中有两个相邻变量X, Y, CPU1和CPU2分别处理这两个变量
CPU2修改Y, 再将缓存回写到内存中, 之后, Cache的Line2失效
CPU1读取X时, Cache需要重新从内存加载, 导致性能损失
Intel缓存相关的指令为
将内存加载到缓存 prefetcht0
, prefetcht1
, prefetcht2
将缓存回写到内存 clflush
, clflushopt
java代码模拟
使用java代码模拟这种情况
public class FalseSharing {
private final static Logger logger = LogManager.getLogger(FalseSharing.class);
public int x;
//强行将x, y划分到不同的Cache line上
//public long pading1, pading2, pading3, pading4, pading5, pading6, pading7, pading8;
public volatile int y;
public static void main(String[] args) throws Throwable{
final FalseSharing share = new FalseSharing();
//线程1不停的读取x值, 测试读取操作的性能.
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
int s = 0;
long time = System.nanoTime();
for(int i=0; i<100*10000; i++){
s += share.x;
}
logger.info("s: {}, time: {}", s,System.nanoTime() - time);
}
});
//线程2不停的修改y值
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0; i<Integer.MAX_VALUE; i++){
share.y++;
}
}
});
t2.start();
t1.start();
t1.join();
logger.info("complete");
}
}
多次测试, x的读取操作时间为
4428746
3757621
4385126
4600377
4075793
解决方法
补齐64字节
将
//强行将x, y划分到不同的Cache line上, 让X与Y至少相隔64字节
public long pading1, pading2, pading3, pading4, pading5, pading6, pading7, pading8;
再测试结果为
3022632
3275231
3371595
3185710
3568599
3261546
速度明显比之前快了很多
java提供了一个注解sun.misc.Contended
将变量或对象空间补足到64字节. 上面的代码可以写成
@sun.misc.Contended public int x;
public volatile int y;