Cache Line 缓存行

缓存行为何存在?

没有缓存行的情况会出现的问题

  • 当我们要求计算机显示某些数据时,这是先不谈深度的“缓存”的概念,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 就可以自己修改自己的数据,不必进行通信,屏蔽了第一段代码的通信过程的消耗


 

猜你喜欢

转载自blog.csdn.net/qq_36623327/article/details/107722173
今日推荐