与 CPU 相比,内存非常慢。从 RAM 中获取数据大约需要 200 个时钟周期,因此通常编写缓存友好代码对性能非常重要。是的,CPU 花费大量时间等待数据。
为什么会这样?嗯,这只是不同种类的记忆。一般来说,创建快速内存的成本更高,因此为了降低成本,为寄存器保留最快的内存。物理距离也可能是速度的限制。您想要快速访问的内存需要靠近核心。光以大约 300 000 公里/秒的速度传播,这意味着大约 0.3 毫米/纳秒。如果内存在 0.3 毫米之外,物理上不可能在一纳秒内获取数据。 RAM 通常距离 10 厘米,因此在大约 30 纳秒内无法访问。现代 CPU:s 以 GHz 的频率工作,因此我们已经达到了不可能(不难,不可能)使内存跟上 CPU 的障碍。
但是,这种物理限制(相对论)只影响访问时间,而不影响带宽。因此,当您在地址 addr 获取数据时,也无需花费任何额外费用来获取 addr+1。
在寄存器和 RAM 之间有缓存。在现代计算机中,它通常是三层缓存。这与将硬盘驱动器中的数据缓存在 RAM 中时的工作方式类似。当你读取一点数据时,很可能你很快就会需要周围的数据,所以同时读取周围的数据并存储在缓存中。当你请求下一条数据时,它很可能在缓存中。每当您从内存中请求某些内容时,都会有电路检查该内存是否已存在于缓存中。
您无法直接控制缓存。您可以做的是编写缓存友好的代码。对于高级情况,这可能会很棘手,但总的来说,诀窍是不要在内存中跳跃很长的距离。尝试顺序访问内存。
这里是一个如何编写缓存友好的简单示例:
int *squareMatrix=malloc(SIZE*SIZE*sizeof(*squareMatrix));
int sum=0;
for(int i=0; i<SIZE; i++)
for(int j=0; j<SIZE; j++)
sum+=squareMatrix[i*SIZE+j];
还有一个非缓存友好的版本:
int *squareMatrix=malloc(SIZE*SIZE*sizeof(*squareMatrix));
int sum=0;
for(int i=0; i<SIZE; i++)
for(int j=0; j<SIZE; j++)
sum+=squareMatrix[j*SIZE+i];
区别在于[j*SIZE+i] 与[i*SIZE+j]。第一个版本顺序读取整个矩阵,大大增加了下一个元素在您请求时已经在内存中的机会。
这是我电脑上上面代码与SIZE=30000的区别:
$ time ./fast
real 0m2.755s
user 0m2.516s
sys 0m0.236s
$ time ./slow
real 0m18.609s
user 0m18.268s
sys 0m0.340s
如您所见,这会显着影响性能。
不同类型内存的典型访问时间和大小。非常近似,只是为了大致了解一下:
Memory type # Clock tics Size
===================|================|=============
register | 1 | 8B each, around 128B total
level1 cache | 5 | 32kB
level2 cache | 10 | 1MB
level3 cache | 50 | 20MB
RAM | 200 | 16GB
SSD drive | 10,000 | 500GB
Mechanical drive | 1,000,000 | 4TB
还可以提到,一级缓存通常分为数据和代码。