CPU速度是否受从内存中获取指令的速度限制?

Qia*_*ian 4 memory cpu performance cpu-architecture

在学习汇编时,我意识到我应该将经常访问的数据放在寄存器中而不是内存中,因为内存要慢得多。

问题是,由于首先要从内存中获取指令,CPU如何比内存运行得更快?CPU通常会花费大量时间等待内存中的指令吗?

编辑:要运行程序,我们需要将其编译为包含机器代码的文件。然后,我们将该文件加载到内存中,然后逐条执行一条指令。CPU需要知道要运行的指令,然后从内存中获取那条信息。我不是在问要处理数据,而是要从内存中读取指令的过程。对不起,如果我不够清楚。

编辑2

示例:在我的计算机上xor eax, eax编译为31c0。我知道这条指令本身很快。但是要清除eax,CPU需要31c0首先从内存中读取。如果访问内存的速度很慢,那么读取操作将花费大量时间,而在此期间,CPU只是停顿了?

Pet*_*des 6

与指令执行并行进行的代码提取至关重要,甚至8086都做到了(在一定程度上,预取缓冲区非常小,带宽很低)。即使这样,代码获取带宽实际上仍然是8086的主要瓶颈。

(我只是意识到您没有标记此x86,尽管您确实使用x86指令作为示例。我所有的示例都是x86,但是其他任何体系结构的基本知识都差不多。除了非x86 CPU会如果不使用已解码的uop缓存,则x86是唯一仍然普遍使用的ISA,它很难解码,因此值得将解码结果进行缓存。)


在现代CPU中,代码获取很少成为瓶颈,因为高速缓存和预取隐藏了延迟,并且与数据所需的带宽相比,带宽要求通常较低。(但是,占用代码量很大的膨胀代码可能会因指令高速缓存未命中而变慢,从而导致前端停顿。)

L1I缓存与L1D缓存是分开的,并且CPU每个周期获取/解码一个至少16字节x86代码的块。具有解码uop缓存(Intel Sandybridge系列和AMD Ryzen)的CPU甚至还可以缓存已经解码的指令,以消除解码瓶颈。

请参阅http://www.realworldtech.com/sandy-bridge/3/,以获取有关Intel Sandybridge中前端的相当详细的内容(fetch / pre-decode / decode / rename + issue),并带有诸如以下的框图这显示了英特尔Sandybridge与英特尔Nehalem以及AMD Bulldozer的指令获取逻辑。(解码在下一页)。“预解码”阶段找到指令边界(即,在解码每个指令实际上是什么之前先解码指令长度)。

大卫·坎特(David Kanter)的SnB写作

L1I高速缓存未命中导致对统一L2的请求。现代x86 CPU还具有共享的L3缓存(在多个内核之间共享)。

硬件预取将即将需要的代码带到L2和L1I中,就像将数据预取到L2和L1D中一样。大多数情况下,这对DRAM隐藏了200多个周期的延迟,通常仅在跳转到“冷”功能时失败。当运行很长的代码序列而没有分支时,它几乎总是可以领先于解码/执行,除非其他东西(例如数据加载/存储)耗尽了所有内存带宽。

您可以构建一些以每个周期16个字节解码的代码,该代码可能高于主内存带宽。甚至在AMD CPU上甚至更高。但是通常,解码瓶颈不仅会限制纯粹的代码获取带宽,还会限制您的工作。


另请参阅Agner Fog的微体系结构指南,以了解有关各种微体系结构的前端的更多信息,并为它们优化asm。

另请参见标签Wiki 中的其他CPU性能链接。


klu*_*utt 3

与CPU相比,内存的速度非常慢。从 RAM 中获取数据大约需要 200 个时钟周期,因此一般来说,编写缓存友好的代码对于性能非常重要。是的,CPU 花费大量时间等待数据。

为什么会出现这种情况呢?嗯,这只是不同类型的记忆。一般来说,创建快速存储器的成本更高,因此为了降低成本,最快的存储器被保留给寄存器。物理距离也可能成为速度的限制。想要快速访问的内存需要靠近核心。光的传播速度约为 300 000 公里/秒,即 0.3 毫米/纳秒左右。如果内存距离为 0.3 毫米,则物理上不可能在一纳秒内获取数据。RAM 通常距离 10 厘米,因此物理上不可能在 30 纳秒左右的时间内进行访问。现代CPU的工作频率为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];
Run Code Online (Sandbox Code Playgroud)

还有一个非缓存友好的版本:

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];
Run Code Online (Sandbox Code Playgroud)

区别在于[j*SIZE+i]vs [i*SIZE+j]。第一个版本按顺序读取整个矩阵,大大增加了当您请求时下一个元素已经在内存中的机会。

这是上面的代码在我的电脑上与 =30000 的差异SIZE

$ time ./fast

real    0m2.755s
user    0m2.516s
sys     0m0.236s

$ time ./slow

real    0m18.609s
user    0m18.268s
sys     0m0.340s
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,这会显着影响性能。

不同类型内存的典型访问时间和大小。非常近似,只是给出一个大概的想法:

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
Run Code Online (Sandbox Code Playgroud)

还可以提到的是,一级缓存通常分为数据和代码。