Performance 为什么当我的循环包含在一个缓存线中时会快得多?

Performance 为什么当我的循环包含在一个缓存线中时会快得多?,performance,caching,cpu,Performance,Caching,Cpu,当我在Ryzen 9 3900X上运行这个小汇编程序时: _start: xor rax, rax xor rcx, rcx loop0: add rax, 1 mov rdx, rax and rdx, 1 add rcx, rdx cmp rcx, 1000000000 jne

当我在Ryzen 9 3900X上运行这个小汇编程序时:

_start:   xor       rax, rax
          xor       rcx, rcx
loop0:    add       rax, 1
          mov       rdx, rax
          and       rdx, 1
          add       rcx, rdx
          cmp       rcx, 1000000000
          jne       loop0
如果loop0到jne(包括jne)之间的所有指令都完全包含在一个缓存线中,则它将在450毫秒内完成。即,如果:

round((loop0的地址)/64)=round((jne指令结束的地址)/64)

但是,如果上述条件不成立,则循环需要900毫秒

我已经用代码进行了回购

为什么在某些特定情况下,内部循环会慢得多


编辑:从测试错误中删除带有结论的声明。

您的问题在于
jne
指令的可变成本

首先,为了理解效果的影响,我们需要分析整个循环本身。Ryzen 9 3900X的体系结构是Zen2。我们还可以在或上检索有关此的信息。 该体系结构有4个ALU和3个AGU。这大致意味着它每个周期最多可以执行4条指令,如add/and/cmp

以下是循环的每条指令的成本(基于for Zen1):

#交互吞吐量
循环0:加上rax,1#0.25
mov-rdx,rax#0.2
和rdx,1#0.25
加上rcx,rdx#0.25
cmp rcx,100000000#0.25 |熔接0.5-2(如果跳跃,则为2)
jne环0#0.5-2|
如您所见,循环的4条第一计算指令可以在~1个周期内执行。处理器可以将最后两条指令合并为更快的指令。 您的主要问题是,与循环的其余部分相比,最后的
jne
指令可能非常慢。因此,您很可能只测量此指令的开销。从这一点开始,事情开始变得复杂起来

工程师和研究人员在过去几十年中努力工作,以(几乎)任何价格降低此类指令的成本。如今,处理器(如Ryzen 9 3900X)使用无序指令调度来尽快执行
jne
指令所需的相关指令。大多数处理器还可以预测在
jne
之后要执行的下一条指令的地址,并在执行当前循环迭代的另一条指令时获取新指令(例如,下一个循环迭代的指令)。 尽快执行提取对于防止处理器执行管道中出现任何暂停(尤其是在您的情况下)非常重要

从AMD文档“AMD系列17h型号30h及以上处理器的软件优化指南”中,我们可以阅读:

  • 2.8.3回路校准:

    对于处理器来说,循环对齐通常不是一个重要问题。然而,对于热循环,进一步了解权衡可能会有所帮助。由于处理器可以在每个周期读取一个对齐的64字节提取块,因此如果可能,最好将循环的结尾与64字节缓存线的最后一个字节对齐

  • 2.8.1.1下一个地址逻辑

    下一个地址逻辑确定指令提取的地址。[...]. 当确定分支时,分支目标和分支方向预测硬件将重定向下一个地址逻辑,以生成非顺序的提取块地址。以下各节详细介绍了用于预测分支后执行的下一条指令的处理器设施

因此,对位于另一缓存线中的指令执行条件分支会引入额外的延迟开销,因为如果整个循环适合1个缓存线,则不需要提取Op缓存(比L1快的指令缓存)。事实上,如果循环穿过缓存线,则需要进行2次缓存线回迁,这需要不少于2个周期。如果整个循环适合缓存线,则只需要一个1行缓存获取,这只需要1个周期。因此,由于循环迭代非常快,因此额外支付1个周期会导致显著的减速。但是多少钱

你说这个程序大约需要450毫秒。 由于Ryzen 9 3900X turbo频率为4.6 GHz,并且循环执行了2e9次,因此每次循环迭代的循环数为1.035。请注意,这比我们之前预期的要好(我猜这个处理器能够重命名寄存器,忽略
mov
指令,在仅1个周期内并行执行
jne
指令,而循环的其他指令完全是流水线的;这是令人震惊的)。这还表明,支付1个周期的额外获取开销将使执行每个循环迭代所需的周期数增加一倍,从而使总体执行时间增加一倍

如果您不想支付此开销,请考虑<强>展开循环,以显著减少条件分支和非顺序获取的数量。


此问题可能发生在其他体系结构上,如英特尔Skylake。事实上,i5-9600KF上的同一个循环需要0.70秒的循环对齐时间和0.90秒的循环不对齐时间(这也是由于额外的1周期提取延迟)。使用8倍展开,结果为0.53秒(无论对齐方式如何)。

哦,伙计,非常感谢你给出了这个极好的答案!这很有道理。我同意Ryzen9在这个特定循环中的性能(当它对齐时)非常令人印象深刻。不是每条指令都对它前面的指令有数据依赖关系吗?除非指令以某种方式合并,否则它怎么可能在每个指令上运行快于1个周期呢?0.25的交互吞吐量通常只有在数据已经可用的情况下才有效,对吗?今天的处理器非常复杂和“聪明”。它们可以推测地执行多个循环迭代。他们可以