64 位跳转的成本,第一次总是 10-22 个周期?

Sim*_*mon 0 x86 x86-64 micro-optimization

在 x86_64 中,没有 64 位地址的直接跳转。只有一个 32 位的。通过间接跳转,我理解在分支预测发挥作用之前必须解决管道一次。我的问题是:在 64 位中没有办法在第一次执行时进行 1-3 个周期的跳转吗?

Pet*_*des 5

即使没有 I-cache 未命中,直接跳转也不总是“第一次”那么便宜。他们仍然需要分支预测。


在长模式下,jcc rel32jmp rel32(和 rel8 紧凑版本)使用来自 RIP 的符号扩展相对位移。您可以跳转到任何 64 位地址,只要您来自 2GB 以内的地址。因此,请将您的代码与其他代码保持在 2GB 以内,以便您可以使用 rel32 位移。

长模式下没有绝对的直接跳转。32 位模式的 far JMP ptr16:32(操作码 0xEA)和 far CALL ptr16:32 根本没有 64 位版本。(无论如何,为了性能和方便起见,您都不想要远 jmp。)SYSCALL 和 INT 等指令是间接跳转(具有隐式目标),无论如何都没有用。


也没有指令预取/预解码指令来使 L1 I-cache 或 uop 高速缓存中的目标变热,或者任何方式来暗示将很快需要从给定地址解码指令的管道。(请参阅Darek Mihocka 的关于模拟器中间接跳转的文章中PREDECODE 愿望清单部分,其中让一条访客指令的处理程序直接跳转到下一条访客指令的处理程序是很有用的,而不是让一条间接调用调度指令几乎总是预测错误。)


即使直接跳转也需要分支目标缓冲区来预测下一个获取块应该来自其他地方。比解码阶段更早需要此信息,因此必须对其进行预测以避免显着的前端气泡。最近一个有趣的问题提出了这个问题:Slow jmp-instructionRealworldtech 论坛帖子上的回复清楚地表明,分支预测需要在 fetch 块上工作,而不仅仅是指令,即使在易于解码的固定 insn 宽度 ISA(与 x86 不同)上,您也需要比 x86 更早的预测解码结果。


对于新看到的直接 (rel32) 跳转,1-3 个周期对于代码提取气泡的大小是不现实的。但是,该气泡的一部分可能被解码的 uop 队列隐藏。

解码的代码提取可能至少需要 5 或 6 个周期,甚至更多。假设 L1-I 命中时间是 4 个周期,与 Haswell 的 L1D 负载使用延迟相同。然后 Intel CPU 进行预解码以标记指令边界,然后解码阶段最多解码 4 uop。 David Kanter 在 Haswell 的文章中有一张前端图

来自慢速 jmp 指令问题的 OP 数据表明,在英特尔 Broadwell(分支目标 = 下一个 insn)上,除了 JMP 指令之外一大块什么都没有运行,大约每 12 个时钟一个 JMP,所以这是最坏的情况,其中 fetch/解码气泡根本无法隐藏,因为您没有做任何其他事情让前端有时间赶上。

我假设我们正在谈论从传统解码器运行。从 uop 缓存运行时的BTB 未命中可能会稍微短一些,因为解码后的 uop 可用速度更快。如果分支目标也在 uop 缓存中命中,那么在解码的 uop 可以开始进入解码的 uop 队列(用作循环缓冲区的相同缓冲区)之前,周期也会更少。

如果在代码提取气泡期间解码的 uop 队列不为空,那么在问题阶段可能没有任何气泡(将 uop 发送到 CPU 的乱序部分)。

或者,如果 OOO 部分有很多未执行的 uops 需要处理(即 CPU 正在执行一些具有瓶颈的代码,这些代码将 IPC 限制为远小于前端带宽),前端气泡可能不会对其产生太大影响.


不过,间接分支更糟。最多只能在几个周期后才能检测到正确的目标。 您的基本前提是正确的:它们并不便宜,应尽可能避免。

  • 如果有人能解释为什么这值得投反对票,我很乐意做出更正。我认为它是准确的,并解决了问题中的假设。 (2认同)