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

时间:2017-08-22 13:33:51

标签: performance memory cpu cpu-architecture

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

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

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

编辑2

示例:xor eax, eax在我的计算机上编译为31c0。我知道这条指令本身很快。但要清除eax,CPU需要先从内存中读取31c0。如果访问内存很慢,那读取应该花费很多时间,并且在这段时间内CPU只会停止?

3 个答案:

答案 0 :(得分:4)

如果您经常访问数据,则可能还会重复处理相同的说明。高效的CPU不会从慢速内存中反复获取相同的指令。相反,它们被置于指令缓存中,访问时间非常短。因此,cpu通常不需要等待指令。

答案 1 :(得分:4)

与指令执行并行执行代码非常重要,即使是8086也是如此(在有限的范围内,使用非常小的预取缓冲区和低带宽)。即便如此,代码获取带宽实际上也是8086的主要瓶颈。

(我刚刚意识到你没有标记这个x86,虽然你确实使用了x86指令作为例子。我的所有例子都是x86,但是对于任何其他架构来说基本都是一样的。除了非x86之外CPU不会使用解码的uop缓存,x86是唯一仍然常用的ISA,它很难解码,因此值得缓存解码结果。)

在现代CPU中,代码提取很少成为瓶颈,因为caches和预取会隐藏延迟,并且与数据所需的带宽相比,带宽要求通常较低。 (具有非常大的代码占用空间的膨胀代码可能会因指令缓存未命中而导致速度减慢,导致前端停滞。)

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

请参阅http://www.realworldtech.com/sandy-bridge/3/,了解英特尔Sandybridge(获取/预解码/解码/重命名+问题)中前端的相当详细的说明,使用这样的方框图,显示英特尔Sandybridge与英特尔Nehalem和AMD Bulldozer的指令获取逻辑。 (解码在下一页)。 “预解码”阶段找到指令边界(即,在解码每条指令实际上是什么之前解码指令长度)。

David Kanter's SnB writeup

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

硬件预取将很快需要的代码带入L2和L1I,就像数据预取到L2和L1D一样。这隐藏了>大多数时候DRAM的200周期延迟,通常只有跳转到“冷”功能才会失败。除非其他东西(如数据加载/存储)耗尽所有内存带宽,否则在运行长序列代码时,它几乎总能保持在解码/执行之前,除非其他东西(如数据加载/存储)耗尽所有内存带宽。

您可以构造一些代码,每个周期解码16个字节,这可能高于主内存带宽。或者甚至更高的AMD CPU。但通常解码瓶颈会限制你比纯粹的代码获取带宽更多。

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

另请参阅标记wiki中的其他CPU性能链接。

答案 2 :(得分:1)

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

为什么会这样?嗯,这只是不同种类的记忆。通常,创建快速存储器的成本更高,因此为了降低成本,最快的存储器保留给寄存器。物理距离也可以是速度的限制。您想要快速访问的内存需要靠近核心。光速以大约300 000km / s的速度传播,这意味着大约0.3mm / ns。如果存储器距离0.3mm,则在物理上不可能在1纳秒内获得数据。 RAM通常距离10厘米,因此在30ns左右的时间内无法访问。现代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];

非缓存友好版本:

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] vs [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

如您所见,这可能会显着影响性能。

不同类型内存的典型访问时间。非常近似,只是为了大致了解它:

register      1 clock tic
level1 cache  5 clock tics
level2 cache  10 clock tics
level3 cache  50 clock tics
RAM           200 clock tics