gla*_*des 6 c++ performance x86 assembly branch-prediction
我发现了这个非常漂亮的信息图,它对某些操作所使用的 CPU 周期进行了粗略估计。在学习时,我注意到一个条目“if的右分支”,我认为如果满足条件,该分支将采用“if”(编辑:正如评论中指出的“右”实际上意味着“正确预测的分支” )。这让我想知道 if 分支与 else 分支相比是否存在任何(即使是很小的)速度差异。
例如,比较以下非常简洁的代码:
#include <cstdio>
volatile int a = 2;
int main()
{
if (a > 5) {
printf("a > 5!");
a = 2;
} else {
printf("a <= 5!");
a = 3;
}
}
Run Code Online (Sandbox Code Playgroud)
它在 x86 64 位中生成此程序集:
.LC0:
.string "a > 5!"
.LC1:
.string "a <= 5!"
main:
push rcx
mov eax, DWORD PTR a[rip]
cmp eax, 5
jle .L2
mov edi, OFFSET FLAT:.LC0
xor eax, eax
call printf
mov DWORD PTR a[rip], 2
jmp .L3
.L2:
mov edi, OFFSET FLAT:.LC1
xor eax, eax
call printf
mov DWORD PTR a[rip], 3
.L3:
xor eax, eax
pop rdx
ret
a:
.long 2
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,为“a > 5”调用 printf 的右侧分支更接近 cmp - 指令。因此,查看数据高速缓存时,可能会出现这样的情况:调用 printf 的指令已加载到高速缓存行中,而必须首先从 L2 或 L3 高速缓存中获取 else 分支。这(理论上)会导致在分支预测模式建立之前“右”分支的微小加速吗?
简而言之,在主流处理器上不行,但在某些旧/嵌入式处理器上可以。
现代主流处理器非常擅长预测条件(假设它们不是第一次执行或者测试可以提前完成)。当正确预测分支时,几乎没有成本(除了使用在某些特定端口上执行的分支单元之外)。处理器可以推测性地获取并执行预测的条件块的指令。当分支未正确预测时,处理器必须执行一种相当昂贵的回滚操作(通常为 5-15 个周期)。
一些嵌入式处理器和旧处理器使用静态预测算法。例如,他们可以假设该分支从未被采用。在此类处理器上,执行if块通常比else假设编译器不重新排序块(启用优化时非常频繁)的执行速度更快。开发人员可以提供内置提示,以帮助编译器生成可以由使用静态分区的处理器更有效地执行的代码。配置文件引导优化可用于自动查找通常为真/假的条件,并相应地重新排序分支以提高性能。
主流(服务器、桌面和高端移动)处理器主要使用动态预测算法。例如,他们可以跟踪并记住某个分支何时被采用或不被采用,以便知道将来该分支被采用的概率。处理器通常跟踪一组有限的条件分支。因此,当代码具有太多(叠瓦式)条件分支指令时,可以使用静态分区算法作为后备方法。
值得一提的是,在某些情况下可以重置/刷新预测信息,例如当进程被抢占时(正如@HenriqueBucher 所指出的)。这意味着当存在许多上下文切换时,预测的效率可能会低得多。请注意,推测可以由某些特定指令集部分控制,以减轻Spectre等漏洞。
由于处理器需要获取可能不在指令高速缓存中的指令,因此跳转到远不可预测的位置可能会很昂贵。就您而言,这在主流 x86-64 处理器上当然并不重要,因为最近的 x86-64 处理器预计会很快地将程序的所有指令加载到缓存中。例如,Skylake 处理器可以从指令缓存中获取 16 字节/周期,而 Zen 2 处理器则可以达到 32 字节/周期。两者都可以将 64 字节缓存线加载到指令缓存中。
关于最新英特尔处理器的分支预测算法的公开信息并不多。AMD Zen 2+ 处理器中的一款已经有很好的记录:它使用高效的TAGE 预测器与感知器相结合,以便根据过去的统计数据来预测某种情况的结果。您可以在此处找到有关其行为的详细信息。
| 归档时间: |
|
| 查看次数: |
896 次 |
| 最近记录: |