CPU的多级缓存
CPU缓存通常分为大小不等的三级缓存
来自百度百科对三级缓存分类的介绍:
- 一级缓存都内置在CPU内部并与CPU同速运行,可以有效的提高CPU的运行效率。一级缓存越大,CPU的运行效率越高,但受到CPU内部结构的限制,一级缓存的容量都很小。
- 二级缓存,它是为了协调一级缓存和内存之间的速度。cpu调用缓存首先是一级缓存,当处理器的速度逐渐提升,会导致一级缓存就供不应求,这样就得提升到二级缓存了。二级缓存它比一级缓存的速度相对来说会慢,但是它比一级缓存的空间容量要大。主要就是做一级缓存和内存之间数据临时交换的地方用。
- 三级缓存是为读取二级缓存后未命中的数据设计的—种缓存,在拥有三级缓存的CPU中,只有约5%的数据需要从内存中调用,这进一步提高了CPU的效率。其运作原理在于使用较快速的储存装置保留一份从慢速储存装置中所读取数据并进行拷贝,当有需要再从较慢的储存体中读写数据时,缓存(cache)能够使得读写的动作先在快速的装置上完成,如此会使系统的响应较为快速。
效果演示
1、按行写入
public class CacheLine {
public static void main(String[] args) {
int[][] arr = new int[10000][10000];
long s = System.currentTimeMillis();
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
//按行写入
arr[i][j] = 0;
}
}
long e = System.currentTimeMillis();
System.out.println(e-s);
}
}
2、按列写入
public class CacheLine {
public static void main(String[] args) {
int[][] arr = new int[10000][10000];
long s = System.currentTimeMillis();
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr[i].length; j++) {
//调整顺序,按列写入
arr[j][i] = 0;
}
}
long e = System.currentTimeMillis();
System.out.println(e-s);
}
}
上面两个案例都能完成二维数组的遍历写入,但是按行写入的效率要高于按列写入,读者可以自行测试,这就是CPU缓存行带来的影响。
原因分析
CPU的缓存是由多个缓存行组成的,以缓存行为基本单位,一个缓存行的大小一般为64字节,二维数组在内存中保存时,实际上是以按行遍历的方式进行保存,比如:
arr[0][0],arr[0][1],arr[1][0],arr[1][1],arr[2][0],arr[2][1]...
所以当按行访问时,是按照内存存储的顺序进行访问,那么CPU缓存后面的元素就可以利用到,而如果是按列访问,那么CPU的缓存是没有用的。
缓存行对齐
public class CacheLinePadding {
private static class Padding {
//一个long是8个字节,一共7个long
//public volatile long p1, p2, p3, p4, p5, p6, p7;
}
private static class T extends Padding {
//x变量8个字节,加上Padding中的变量,刚好64个字节,独占一个缓存行。
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (long i = 0; i < 10000000; i++) {
arr[0].x = i;
}
});
Thread t2 = new Thread(() -> {
for (long i = 0; i < 10000000; i++) {
arr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start) / 100000);
}
}
上述代码,可以自行测试,当有如下变量时,执行的速度会比没有时更快。
public volatile long p1, p2, p3, p4, p5, p6, p7;
JDK8提供的解决方式
JDK8版本之后,提供了解决伪共享的注解,需要在启动时添加如下配置-XX:-RestrictContended,然后使用sun.misc.Contended注解即可。
public class CacheLinePadding {
@Contended
private static class T {
public volatile long x = 0L;
}
public static T[] arr = new T[2];
static {
arr[0] = new T();
arr[1] = new T();
}
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(() -> {
for (long i = 0; i < 10000000; i++) {
arr[0].x = i;
}
});
Thread t2 = new Thread(() -> {
for (long i = 0; i < 10000000; i++) {
arr[1].x = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start) / 100000);
}
}