1.1 CPU缓存
在现代计算机当中,CPU是大脑,最终都是由它来执行所有的运算。而内存(RAM)则是血液,存放着运行的数据;但是,由于CPU和内存之间的工作频率不同,CPU如果直接去访问内存的话,系统性能将会受到很大的影响,所以在CPU和内存之间加入了三级缓存,分别是L1、L2、L3。
当CPU执行运算时,它首先会去L1缓存中查找数据,找到则返回;如果L1中不存在,则去L2中查找,找到即返回;如果L2中不存在,则去L3中查找,查到即返回。如果三级缓存中都不存在,最终会去内存中查找。对于CPU来说,走得越远,就越消耗时间,拖累性能。
在三级缓存中,越靠近CPU的缓存,速度越快,容量也越小,所以L1缓存是最快的,当然制作的成本也是最高的,其次是L2、L3。
CPU频率,就是CPU运算时的工作的频率(1秒内发生的同步脉冲数)的简称,单位是Hz。主频由过去MHZ发展到了当前的GHZ(1GHZ=103MHZ=106KHZ= 10^9HZ)。
内存频率和CPU频率一样,习惯上被用来表示内存的速度,内存频率是以MHz(兆赫)为单位来计量的。目前较为主流的内存频率1066MHz、1333MHz、1600MHz的DDR3内存,2133MHz、2400MHz、2666MHz、2800MHz、3000MHz、3200MHz的DDR4内存。
可以看得出,如果CPU直接访问内存,是一件相当耗时的操作。
1.2 缓存行
当数据被加载到三级缓存中,它是以缓存行的形式存在的,不是一个单独的项,也不是单独的指针。
在CPU缓存中,数据是以缓存行(cache line)为单位进行存储的,每个缓存行的大小一般为32--256个字节,常用CPU中缓存行的大小是64字节;CPU每次从内存中读取数据的时候,会将相邻的数据也一并读取到缓存中,填充整个缓存行;
可想而知,当我们遍历数组的时候,CPU遍历第一个元素时,与之相邻的元素也会被加载到了缓存中,对于后续的遍历来说,CPU在缓存中找到了对应的数据,不需要再去内存中查找,效率得到了巨大的提升;
但是,在多线程环境中,也会出现伪共享的情况,造成程序性能的降低,堪称无形的性能杀手;
/**
* @描述 64位机,一个对象大头16 个字节,一个 long 类型 8个字节 6*8 +16 = 64个字节 占满一个缓存
* @参数 $ CPU缓存中,数据是以缓存行(cache line)为单位进行存储
* @返回值 $
* @创建人
* @创建时间 $
* @修改人和其它信息
*/
public class CacheTest {
///数足够大才能 体现,数据小的时候 结果相反 因为其它数据会被加载导 一级 二级 缓存,导致结果相反。
public static int length = 1024*1024;
public static long [][] longs;
public static void main(String []args) throws InterruptedException {
//休眠3秒加载静态文件
Thread.sleep(3000);
longs = new long[length][];
for (int i = 0 ; i < length ; i++){
longs[i] = new long[6];
for (int j = 0 ; j < 6 ;j++){
longs[i][j] = 1l;
}
}
hitCache();
catchMiss();
}
/**
* 演示缓存命中
*/
public static void hitCache(){
long start = System.nanoTime();
int sum = 0;
for (int x = 0 ; x < length ; x ++){
for (int y = 0 ; y<6 ; y++ ){
sum += longs[x][y];
}
}
System.out.print("hitCache: ");
System.out.println(System.nanoTime() - start);
}
/**
* 缓存位未命中
*/
public static void catchMiss(){
long start = System.nanoTime();
int sum = 0;
for (int y = 0 ; y < 6; y++){
for (int x = 0 ; x<length ; x++){
sum += longs[x][y];
}
}
System.out.print("catchMiss: ");
System.out.println(System.nanoTime() - start);
}
}
参考博客:https://www.jianshu.com/p/1309dd03f81a