现代 CPU 中的小分支
Small branches in modern CPUs
像 Kaby Lake 这样的现代 CPU 如何处理小分支? (在下面的代码中,它是跳转到标签 LBB1_67)。据我所知,分支不会有害,因为跳转不如 16 字节块大小,这是解码的大小 window.
或者是否有可能由于某些宏操作融合,分支将被完全删除?
sbb rdx, qword ptr [rbx - 8]
setb r8b
setl r9b
mov rdi, qword ptr [rbx]
mov rsi, qword ptr [rbx + 8]
vmovdqu xmm0, xmmword ptr [rbx + 16]
cmp cl, 18
je .LBB1_67
mov r9d, r8d
.LBB1_67: # in Loop: Header=BB1_63 Depth=1
vpcmpeqb xmm0, xmm0, xmmword ptr [rbx - 16]
vpmovmskb ecx, xmm0
cmp ecx, 65535
sete cl
cmp rdi, qword ptr [rbx - 32]
sbb rsi, qword ptr [rbx - 24]
setb dl
and dl, cl
or dl, r9b
在任何 x86 CPUs 中都没有短分支距离的特殊情况。即使是无条件 jmp
到下一条指令(架构上是 nop)也需要正确的分支预测才能有效处理;如果你连续放置足够多的那些,你就会 运行 出 BTB 条目并且性能会下降。
Fetch/decode只是小问题;是的,同一缓存行中的一个非常短的分支仍会命中 L1i,并且可能会命中 uop 缓存。但是解码器不太可能对预测采取的前向跳转进行特殊处理,并利用从一个包含分支和目标的块中发现的预解码指令边界。
当指令被解码为微指令并送入前端时,寄存器值不可用;这些仅在乱序执行后端可用。
主要问题是当.LBB1_67:
之后的指令执行时,架构状态根据分支是否被采用而不同。
微架构状态也是如此(RAT = 寄存器分配 Table)。
或者:
r9
取决于 sbb
/setl
结果(mov r9d, r8d
没有 运行)
r9
取决于 sbb
/setb
结果(mov r9d, r8d
做了 运行)
条件分支在计算机体系结构术语中称为 "control dependencies"。分支预测+推测执行避免了将控制依赖转化为数据依赖。如果预测 je
未被采用,则 setl
结果(r9
的旧值)将被 mov
覆盖,并且在任何地方都不再可用。
在 je
中检测到错误预测(实际上应该被采取)后,无法从中恢复,尤其是在一般情况下。当前的 x86 CPUs 不会尝试寻找重新加入所采用路径的失败路径或弄清楚它所做的任何事情。
如果 cl
很长时间没有准备好,那么很长时间都没有发现错误预测,那么 or dl, r9b
之后的许多指令可能会使用错误的输入执行。在一般情况下,可靠+高效恢复的唯一方法是丢弃所有根据 "wrong" 路径的指令完成的工作。检测到 vpcmpeqb xmm0, [rbx - 16]
例如仍然 运行 是困难的,并且没有寻找。 (现代英特尔,自 Sandybridge 以来,有一个分支顺序缓冲区 (BOB),可以在分支上对 RAT 进行快照,一旦执行检测到它,就可以有效地回滚到分支未命中,同时仍然允许在 上乱序执行较早的 指示在回滚期间继续。在此之前,分支未命中必须回滚到退休状态。)
某些非 x86 ISA(例如我认为的 PowerPC)的一些 CPU 已经尝试将恰好跳过 1 条指令的前向分支转换为断言(数据依赖),而不是推测它们。例如动态吊床预测
对于非谓词指令集体系结构 讨论了这个想法,甚至决定是否在每个分支的基础上进行谓词。如果您的分支预测历史表明此分支预测不佳,那么预测它可能会很好。 (Hammock 分支是向前跳转一条或几条指令的分支。在具有固定宽度指令字的 ISA 上(如 RISC)检测恰好 1 个指令案例是微不足道的,但在 x86 上很难。)
在这种情况下,x86 有一个 cmovcc
指令,一个 ALU select 操作,根据标志条件产生两个输入之一。 cmove r9d, r8d
而不是 cmp
/je
可以避免分支预测错误,但代价是引入对 cl
和 [=33 的数据依赖=] 用于使用 r9d
的说明。英特尔 CPU 不要试图为您做这件事。
(在 Broadwell 和后来的 Intel 上,cmov 只有 1 uop,从 2 下降。cmp/jcc 是 1 uop,而 mov
本身也是 1 uop,所以在 not-taken case cmov
前端的 uops 也更少。在 take 的情况下,即使预测正确,taken 的分支也会在管道中引入气泡,这取决于代码的吞吐量:阶段之间的队列是否可以吸收它。)
请参阅 了解 CMOV 比分支慢的情况,因为引入数据依赖性不好。
像 Kaby Lake 这样的现代 CPU 如何处理小分支? (在下面的代码中,它是跳转到标签 LBB1_67)。据我所知,分支不会有害,因为跳转不如 16 字节块大小,这是解码的大小 window.
或者是否有可能由于某些宏操作融合,分支将被完全删除?
sbb rdx, qword ptr [rbx - 8]
setb r8b
setl r9b
mov rdi, qword ptr [rbx]
mov rsi, qword ptr [rbx + 8]
vmovdqu xmm0, xmmword ptr [rbx + 16]
cmp cl, 18
je .LBB1_67
mov r9d, r8d
.LBB1_67: # in Loop: Header=BB1_63 Depth=1
vpcmpeqb xmm0, xmm0, xmmword ptr [rbx - 16]
vpmovmskb ecx, xmm0
cmp ecx, 65535
sete cl
cmp rdi, qword ptr [rbx - 32]
sbb rsi, qword ptr [rbx - 24]
setb dl
and dl, cl
or dl, r9b
在任何 x86 CPUs 中都没有短分支距离的特殊情况。即使是无条件 jmp
到下一条指令(架构上是 nop)也需要正确的分支预测才能有效处理;如果你连续放置足够多的那些,你就会 运行 出 BTB 条目并且性能会下降。
Fetch/decode只是小问题;是的,同一缓存行中的一个非常短的分支仍会命中 L1i,并且可能会命中 uop 缓存。但是解码器不太可能对预测采取的前向跳转进行特殊处理,并利用从一个包含分支和目标的块中发现的预解码指令边界。
当指令被解码为微指令并送入前端时,寄存器值不可用;这些仅在乱序执行后端可用。
主要问题是当.LBB1_67:
之后的指令执行时,架构状态根据分支是否被采用而不同。
微架构状态也是如此(RAT = 寄存器分配 Table)。
或者:
r9
取决于sbb
/setl
结果(mov r9d, r8d
没有 运行)r9
取决于sbb
/setb
结果(mov r9d, r8d
做了 运行)
条件分支在计算机体系结构术语中称为 "control dependencies"。分支预测+推测执行避免了将控制依赖转化为数据依赖。如果预测 je
未被采用,则 setl
结果(r9
的旧值)将被 mov
覆盖,并且在任何地方都不再可用。
在 je
中检测到错误预测(实际上应该被采取)后,无法从中恢复,尤其是在一般情况下。当前的 x86 CPUs 不会尝试寻找重新加入所采用路径的失败路径或弄清楚它所做的任何事情。
如果 cl
很长时间没有准备好,那么很长时间都没有发现错误预测,那么 or dl, r9b
之后的许多指令可能会使用错误的输入执行。在一般情况下,可靠+高效恢复的唯一方法是丢弃所有根据 "wrong" 路径的指令完成的工作。检测到 vpcmpeqb xmm0, [rbx - 16]
例如仍然 运行 是困难的,并且没有寻找。 (现代英特尔,自 Sandybridge 以来,有一个分支顺序缓冲区 (BOB),可以在分支上对 RAT 进行快照,一旦执行检测到它,就可以有效地回滚到分支未命中,同时仍然允许在 上乱序执行较早的 指示在回滚期间继续。在此之前,分支未命中必须回滚到退休状态。)
某些非 x86 ISA(例如我认为的 PowerPC)的一些 CPU 已经尝试将恰好跳过 1 条指令的前向分支转换为断言(数据依赖),而不是推测它们。例如动态吊床预测 对于非谓词指令集体系结构 讨论了这个想法,甚至决定是否在每个分支的基础上进行谓词。如果您的分支预测历史表明此分支预测不佳,那么预测它可能会很好。 (Hammock 分支是向前跳转一条或几条指令的分支。在具有固定宽度指令字的 ISA 上(如 RISC)检测恰好 1 个指令案例是微不足道的,但在 x86 上很难。)
在这种情况下,x86 有一个 cmovcc
指令,一个 ALU select 操作,根据标志条件产生两个输入之一。 cmove r9d, r8d
而不是 cmp
/je
可以避免分支预测错误,但代价是引入对 cl
和 [=33 的数据依赖=] 用于使用 r9d
的说明。英特尔 CPU 不要试图为您做这件事。
(在 Broadwell 和后来的 Intel 上,cmov 只有 1 uop,从 2 下降。cmp/jcc 是 1 uop,而 mov
本身也是 1 uop,所以在 not-taken case cmov
前端的 uops 也更少。在 take 的情况下,即使预测正确,taken 的分支也会在管道中引入气泡,这取决于代码的吞吐量:阶段之间的队列是否可以吸收它。)
请参阅