操作系统使得计算机硬件对程序员透明,即程序员在写代码时不用考虑机器到底如何工作。但是了解CPU内高速缓存的作用能帮助程序员设计出更高效的代码。下面简单介绍一下CPU内的高速缓存和程序优化方法
CPU的高速缓存
计算机中CPU的工作频率(2-4GHz)高于内存的工作频率(1-2GHz)。
但是程序运行时CPU需要从内存中不断读入数据,如果CPU直接从内存中读数据,需要保持和内存同样的频率,会大大损失CPU的性能。CPU内部的高速缓存(SDRAM)就是为了解决这个问题而设计的一个很小的高速缓存区,CPU可以快速地从高速缓存中读取数据,而高速缓存则以较慢的速度批量读取内存中的数据,这样可降低CPU性能的损失。但如果高速缓存中没有CPU需要的数据,高速缓存就要再从内存中读一次数据。
因此如果高速缓存每次从内存中读取的数据被CPU利用率(CPU的命中率)越大,那么CPU做同样次数运算高速缓存从内存中读取数据的次数越少,数据读取消耗时间越少,CPU的性能降低越小;反之高速缓存从内存读取数据的次数越多,数据读取消耗时间越多,CPU性能降低越大。
优化程序的内存读取
因此要使得程序效率提高,就需要让CPU的命中率越大。通常高速缓存从内存中读取数据为连续读取。我们可以比较下面两段代码:
for(unsigned int i = 0; i < n; ++i) {
for (unsigned int k = 0; k < n; ++k) {
for (unsigned int j = 0; j < n; ++j) {
A[i*n + j] += B[i*n + k] * C[k*n + j];
//每次对最内层j循环CPU访问内存B[i*n+k]不变,A[i*n+j]和C[k*n+j]为连续的内存
}
}
}
for(unsigned int i = 0; i < n; ++i) {
for (unsigned int j = 0; j < n; ++j) {
for (unsigned int k = 0; k < n; ++k) {
A[i*n + j] += B[i*n + k] * C[k*n + j];
//每次对最内层k循环CPU访问内存A[i*n+j]不变,B[i*n+k]为连续的内存,C[k*n+j]为离散的内存
}
}
}
这是一个矩阵相乘的代码。上面一段代码j的循环在最里层,k的循环在中间层;下面一段代码k的循环在最里层,j的循环在中间层。
我们发现总是上面的代码运行速度要快一些。因为当j在循环最里层时,可以在n次计算里CPU每次计算仅j不同,CPU只用访问C的一段连续内存。而当k在循环最里层时,CPU不仅要访问B的一段连续内存,还要访问C的不连续内存。因此上面的代码的CPU命中率要高于下面的代码。下面的代码比上面的代码在内存读取上要多花一些时间,使得运行时间增长。
总结
要提高程序内存读写的效率,因为有高速缓存的存在,我们需要提高CPU的命中率,这需要在程序中尽可能地让CPU连续地访问内存,尽量避免跳跃地访问内存。