这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
cpu三级缓存与伪共享
cpu三级缓存
- cpu共有L1 cache、L2 cache、L3 cache三级缓存,速度由高到低。其中L1与L2为cpu核心内共享,L3为所有cpu共享。
- L1、L2、L3缓存的速度从高到底,容量从低到高。因此当cpu读取数据时,都是从L1缓存开始读取,如果没法命中再依次向外读取,最后在内存中读取。以这种方式可以大大的提高响应时间,提高系统的性能。
cpu的缓存一致性
- cpu的缓存会在适当的时刻被刷新到主内存中,而利用缓存一致性的方法,会使得其他cpu看到当前cpu缓存中修改刷新到主存中的数据。
- 数据可能一直不被刷新到主存而是保存在cpu的缓存中,一直到其他数据需要缓存中的信息,才会将cpu的缓存刷新到主存中。只要cpu能够获取到数据,从缓存中或从主存中并没有什么影响。
- cpu缓存一致性自然也会对性能有所影响,但很明显,这种方式比直接在主存中存取的性能要好的多。
伪共享
- 当不同cpu操作的不同变量,存储在同一cpu缓存行中时,就会产生伪共享问题(False sharing)。当一个线程修改其中一个变量时,整个缓存行在另一个cpu中的缓存将失效,需要重新读取无效缓存行的内容,也就是产生了伪共享。
缓存行与缓存行失效
- 当cpu从主存中读取数据时,不会一次只读取一个字节,而是会一次性读取64字节以寻求高效。而一次性读取的64字节也就是一个缓存行。一个缓存行由多个字节组成,也就是会同时存储多个变量。如果多个cpu同时访问同一缓存行的多个变量,则发生了伪共享。
- 当缓存行中变量发生更改时,其他cpu所读取的缓存行数据即变为脏数据,需要从主存中重新读取,而这一过程中cpu无法读取到数据,缓存行失效。
伪共享产生的性能问题
- 当缓存行中的数据由于被其他cpu修改而变为无效,则需要刷新无效的缓存行。因此cpu需要读取缓存中的数据就需要等待缓存行数据刷新,这一过程中就会产生性能损失,cpu在这一时间区间内只能执行更少的指令。
- 伪共享意味着两个cpu不停的对同一缓存行的内容进行写入,导致缓存行反复失效,等待数据刷新,cpu处理的效率就会产生明显的影响。
- 伪共享的解决方案一般是利用数据结构,保证变量存储在不同的缓存行中。
伪共享代码举例
private static volatile long[] a = new long[16];
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000000000; i++) {
a[0] = i;
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000000000; i++) {
a[1] = i;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(System.currentTimeMillis() - start);
long start1 = System.currentTimeMillis();
Thread t3 = new Thread(() -> {
for (int i = 0; i < 1000000000; i++) {
a[2] = i;
}
});
Thread t4 = new Thread(() -> {
for (int i = 0; i < 1000000000; i++) {
a[14] = i;
}
});
t3.start();
t4.start();
t3.join();
t4.join();
System.out.println(System.currentTimeMillis() - start1);
}
- 由于long型所占空间为8个字节,因此可以保证a[2]和a[14]位于不同缓存行中。a[0]和a[1]相邻大概率位于同一缓存行中。经过运行可以轻易发现,伪共享的a[0]和a[1]运行时间明显大于位于不同缓存行的a[2]和a[14];
面对伪共享问题的解决方案
- 使用注解@Contented
- 该注解使用空对象填充的方式,保证变量可以不位于同一缓存行中,可以用于类和变量字段,同时可以设置大小。
- 通过数据结构修改
- 通过数据结构的方式,也就是经过数据结构的调整,让相邻变量不位于同一缓存行中,防止伪共享对性能的浪费。
- 使用disruptor框架
- disruptor采用long变量填充的方式,保证不同的变量一定位于不同的缓存行之中,以此提高性能。
后记
- 千古兴亡多少事?悠悠。不尽长江滚滚流。