手记

并发总结

1 什么是 CPU Cache
CPU Cache 是一种又快又小的存储器,它存在的意义是弥补 Memory 和 CPU 之间的速度差异,因为 CPU 运算速度要比内存读写速度快的多,如:

一次主内存的访问通常在几十到几百个时钟周期。
一次 L1 高速缓存的读写只需要 1-2 个时钟周期。
一次 L2 高速缓存的读写也只需要数十个时钟周期。
按照读取顺序与 CPU 结合的紧密程度,CPU Cache 可以分为以下等级:

一级缓存:简称L1 Cache,位于 CPU 内核的旁边,是与 CPU 结合最为紧密的 CPU Cache。
二级缓存:简称 L2 Cache,分内部和外部两种芯片,内部芯片二级缓存运行速度与主频相同,外部芯片二级缓存运行速度则只有主频的一半。
三级缓存:是为读取二级缓存后未命中的数据设计的一种缓存,在拥有三级缓存的 CPU 中,只有约 5% 的数据需要从内存中调用,这进一步提高了 CPU 的效率。
一级缓存中还分数据缓存 L1d(Data Cache,D-Cache)和指令缓存 L1i(Instruction Cache,I-Cache)。二者分别用来存放数据和执行这些数据的指令:

通过下图可以直观明白各级缓存之间的响应差距,以及内存到底有多慢:

2 什么是 Cache Line
Cache Line 是 CPU Cache 的最小缓存单位,目前主流的 CPU Cache 的 Cache Line 大小都是 64bytes。假设有一个 512B 的一级缓存,按照 64B 的缓存单位大小计算,这个一级缓存所能存放的缓存个数就是 512/64=8 个。

当我们对数组中一个元素的访问时,会使得整条 Cache Line 被填充,从而使后面若干个元素受益于缓存带来的加速。需要注意的是当数组大小大于 64B 时,就至少必须要两条 Cache Line,继而在循环访问时会出现两次 Cache Line 的填充,由于缓存填充的时间远高于数据访问的响应时间,因此多一次缓存填充对于总执行速度的会造成影响。

3 了解 Cache Line 对编码的帮助?
先看下面这段代码:

public class CacheLineExample {
public static void main(String[] args) {
new Thread(() -> {
runFor1(); // 1耗时:618089962
}).start();
new Thread(()-> {
runFor2(); // 2耗时:2276663699
}).start();
}

public static void runFor1() {
    long stratTime = System.nanoTime();
    int[][] array = new int[10000][10000];
    for (int i = 0; i < 10000; i++) {
        for (int j = 0; j < 10000; j++) {
            int num = array[i][j];
        }
    }
    long endTime = System.nanoTime();
    System.out.println("2耗时:"+ (endTime - stratTime));
}

public static void runFor2() {
    long stratTime = System.nanoTime();
    int[][] array = new int[10000][10000];
    for (int i = 0; i < 10000; i++) {
        for (int j = 0; j < 10000; j++) {
            int num = array[j][i];
        }
    }
    long endTime = System.nanoTime();
    System.out.println("1耗时:"+ (endTime - stratTime));
}

}

在这段代码中 runFor1 总是比 runFor2 的执行速度要快。

我们先看 runFor1 的执行情况,首先我们需要加载 array[0][0],在每次加载时都会检查缓存中是否有我们想要的数据,第一次检查当然没有数据,所以将 array[0][n] 加载到需要的缓存行中,然后我们的程序接着访问 array[0][1],检查到了,不需要继续加载,依次类推,假设在不超过 L1d 大小的情况下,程序一共需要加载 n 次数据。

我们再看 runFor2 的执行情况,首先我们加载 array[0][0],缓存中没有我们要的数据,将 array[0][n] 加载到需要的缓存行中,但接下来我们的程序接着访问 array[1][0],没有数据,又将 array[1][n] 加载到缓存行中,依次类推,假设在不超过 L1d 大小的情况下,程序一共需要加载 n*n 次数据。

本文简单的总结了 CPU Cache 的基本常识,希望对目前只明白程序写法,不太清楚内部机制的同学有所帮助。

最后放上一张整体图供参考:

参考资料:

0人推荐
随时随地看视频
慕课网APP