Cache Line 缓存行
程序员文章站
2022-05-26 23:53:03
...
缓存行为何存在?
没有缓存行的情况会出现的问题
- 当我们要求计算机显示某些数据时,这是先不谈深度的“缓存”的概念,CPU 会去内存中寻找我们需要的数据,其实这本身可以说是正确的,
- 只是后来人们发现,当计算机去一块内存中寻找某份数据之后,下一次需要的数据往往在上一次找到的数据的附近
- 这是因为当我们把某种数据存入计算机时,代表这个数据的所有信息会作为一个整体存放到某个位置
- 所以如果是检索数据,当第一次查找的数据的地址是 0 ,那么下一次查找到的数据地地址就很可能就在 0 的附近,甚至就是 1 的位置+ 那么如果我们每次从内存中重新寻找数据,就显得有些呆滞
解决方案
- 为了解决这个问题,计算机中引入缓存行的概念,每次从内存中加载数据的时候,都会把相邻的数据一起返回给CPU,CPU 将数据存到自己的缓存中,下次查找数据时会优先从自己的缓存中查找,如果自己的缓存中没有对应的数据才会从内存中获取
- 那么每次CPU 从内存中拿走多少数据呢?太多会浪费资源,太少又会减少下次从CPU 内存中获取数据的命中率
- Intel 的处理器:64个字节,笔者了解到这是一个工业上测试的结果,认为64个字节是比较好的,并没有严谨的数学依据
链路说明
了解了上面的思想,我们来看一下 CPU 的真实数据链路
- 首先CPU不是每次获取数据都要去内存条中获取数据的,他要先去自己的一级缓存中找,一级缓存中没有,再去二级缓存中找,二级缓存中也没有再去三级缓存,最后才会去主内存中找
- 那么如果CPU真的去主内存中找数据了,他会先把数据存在三级缓存中,然后存在二级缓存中,然后是一级换粗,最后返回给CPU
- 当CPU处理完数据需要些回内存时,也是按照顺序一级一级的写入,最后写回到主内存中
shut up , show me your code !
说了这么多,怎么证明呢?
下面两段代码可以证明这件事情
我们使用数组进行测试,使用 volatile 关键字保证当前数组对另一个线程可见
public static volatile long[] arr = new long[2];
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 10000_0000L; i++) {
arr[0] = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 10000_0000L; i++) {
arr[1] = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
}
------------------------------------ 我是分割线 --------------------------------------
public static volatile long[] arr = new long[16];
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(()->{
for (long i = 0; i < 10000_0000L; i++) {
arr[0] = i;
}
});
Thread t2 = new Thread(()->{
for (long i = 0; i < 10000_0000L; i++) {
arr[8] = i;
}
});
final long start = System.nanoTime();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println((System.nanoTime() - start)/100_0000);
}
我们发现,第二段代码的执行速度更快,但是明显是第一段代码创建的数组更小,为什么执行效率却不如第二段代码呢 ?
这是因为,一个 long 占 8 个字节,第一段代码只创建了一个长度为 2 的数组,这个两个 long 型数据存很有可能被存在一个缓存行中,而我们又对 arr 添加了 volatile 关键字,t1 的 修改状态必须对 t2 可见,所以 t1 修改数据后就要把数据写回到主内存,并且通知 t2 数据被修改,需要重新从主内存获取,我们可以大致理解为,t1 修改后把数据写回主内存,通知 t2 重新获取,t2 获取后在修该,然后又写回主内存通知 ti1 获取,形成一个循环,两个线程之间的通信浪费了很多的性能
而第二段代码则直接创建了一个长度为 16 的数组,第一个数组在 arr[0],第二个数据在 arr[8],我们知道 8 个 long 就是 64 个字节,刚好够一个缓存行的大小,也就是说,arr[0] 和 arr[8] 不会出现在同一个缓存行中,那么 t1 和 t2 就可以自己修改自己的数据,不必进行通信,屏蔽了第一段代码的通信过程的消耗
推荐阅读
-
JSP页面缓存cache技术--浏览器缓存介绍及实现方法
-
使用Nginx反向代理与proxy_cache缓存搭建CDN服务器的配置方法
-
zf框架的zend_cache缓存使用方法(zend框架)
-
Asp.Net Cache缓存使用代码
-
Html5应用程序缓存(Cache manifest)
-
Nginx缓存Cache的配置方案以及相关内存占用问题解决
-
Linux如何清理swap、buffer及cache等缓存
-
Application Cache未缓存文件无法访问无法加载问题
-
使用Flask-Cache缓存实现给Flask提速的方法详解
-
android中图片的三级缓存cache策略(内存/文件/网络)