【问题标题】:Does the branch predictor kick in with this?分支预测器是否与此有关?
【发布时间】:2015-11-04 08:57:32
【问题描述】:

大多数(如果不是所有)现代处理器都使用一种称为“分支预测”的技术,它可以猜测在 if-then-else 分支中的路径。

我有一个关于该计划的问题。假设我们有这段代码,没有特定的语言:

if(someCondition)
{
    // some action
    return someValue;
}
// some other action
return someOtherValue;

从逻辑上讲,该代码等价于该代码:

if(someCondition)
{
    // some action
    return someValue;
}
else
{
    // some other action
    return someOtherValue;
}

分支预测器将“预测”第二个示例中的分支,但第一个示例呢?它会猜吗?什么将被加载到管道中?忽略块中实际代码的影响,这两个示例是否可以提高速度?

我的猜测,它取决于编译器:如果语句是使用跳转实现的(在汇编中),只有在设置了寄存器中的比较标志时才会执行。现在,汇编指令的具体外观取决于编译器。除非每个编译器都有一种通用的处理方式,我怀疑是否存在,否则这是依赖于编译器的。在这种情况下,最新的 Visual Studio C++ 和 GC++ 编译器会发生什么?

正如 hexafraction 所指出的,返回值之间的关系以及someCondition 的确定方式...分支预测器可能不会起作用。让我们只考虑 true 和 false 作为返回值。对于条件,让我们假设它是一个在函数内部或外部预先确定的字段、一个局部变量和一些算术语句。

老实说,我不怀疑条件是局部变量的情况和字段已在同一函数中预先确定的情况之间有什么区别。

【问题讨论】:

  • 请记住,有时编译器可以进行不涉及分支的数值优化。根据您的someCondition 的计算方式以及两个返回值之间的关系,理论上可能在某些情况下可以使用无分支逻辑/位旋转/算术。此外,ARM 等架构具有条件执行,这意味着许多涉及分支的逻辑可以无分支地完成。条件指令携带条件作为操作码的一部分,如果条件不满足,则 inst.变成了 nop。
  • 这两段代码不太可能编译成完全相同的机器代码。如果你想谈论 CPU 行为,请比较汇编/机器代码。
  • 我认为你可以省略“Logically speak”。这两个 sn-ps 完全相同,我希望编译器为它们输出相同的字节码/程序集。所以分支预测器看不到任何差异,并且会以同样的方式对待它们……

标签: performance optimization compiler-optimization branch-prediction


【解决方案1】:

gcc -O3 很可能会使用条件移动指令将其优化为无分支序列。例如在 x86 上

# generate someValue in %rax, the x86-64 ABI's return value register
# generate someOtherValue in %rdi, to pick one at random
    test someCondition   # probably actually test or cmp a register
    cmovz  %rdi, %rax    # copy %rdi to %rax, if the zero flag is set.
    ret

cmov 对其输入和标志都有数据依赖性。条件分支是 control 依赖项。使用 cmov 通常很好,除非它是一个长依赖链的一部分并且分支是相当可预测的。

如果if 块内有更多工作,gcc 将生成条件跳转指令。

# generate someValue in %rax
    test someCondition
    jz  .zero
    ret
.zero:
    # compute someOtherValue.  This work doesn't need to happen at all
    # if we don't end up needing it, unlike in the cmov case
    mov  someOtherValue, %rax
    ret

分支预测在条件跳转指令上运行,而不是在高级构造上运行。如果循环条件为真,则使用相同的指令跳回到循环的顶部。根据 http://agner.org/optimize/ 的说法,最近的 Intel CPU 可以记住多达 64 次循环迭代的模式。因此,如果迭代次数为 64 或更少,则每次运行相同迭代次数的循环不会在最后一次迭代中出现分支错误预测。

因此,分支预测器不是根据指令序列来猜测是否会进行跳转。每个单独的分支指令在执行时都会在分支历史缓冲区中获得一个条目。是的,每个编译器都别无选择,只能使用jcc(条件代码跳转)指令来实现分支/循环。

默认为预测未采用。如果该预测是正确的,CPU 不会从缓存中逐出可能仍然有用的信息以腾出空间。有关更多底层细节,请参阅 Agner Fog 的 microarch 文档。


在 Linux 上,要查看正在运行的分支预测器,您可以使用 perf stat

perf stat /bin/ls  # in some big directory
    ... normal ls output

 Performance counter stats for '/bin/ls':

     10.403069      task-clock (msec)         #    0.094 CPUs utilized
         2,255      context-switches          #    0.217 M/sec
             0      cpu-migrations            #    0.000 K/sec
           190      page-faults               #    0.018 M/sec
    16,612,260      cycles                    #    1.597 GHz
     7,843,399      stalled-cycles-frontend   #   47.21% frontend cycles idle
     5,205,565      stalled-cycles-backend    #   31.34% backend  cycles idle
    20,227,093      instructions              #    1.22  insns per cycle
                                              #    0.39  stalled cycles per insn
     3,975,777      branches                  #  382.173 M/sec
########### These two lines ######
        55,785      branch-misses             #    1.40% of all branches

   0.110765717 seconds time elapsed

Intel Sandybridge (i5 2500k) 在低功耗时钟速度下,使用默认的 cpufreq 调节器,在完成ls 之前不会提高时钟速度。

【讨论】:

    【解决方案2】:

    这两个代码示例之间没有区别。 else 无关紧要,因为不需要在 true 子句的末尾进行分支。即使这不是真的,真子句末尾的分支也不会是有条件的。

    换句话说,代码必须编译为:

      Compute test expression
      Branch if false to false_label
      True action
      Return some value
    False_label;
      False action
      Return some other value
    

    【讨论】:

      猜你喜欢
      • 2014-04-25
      • 2014-03-03
      • 2018-07-06
      • 2015-09-20
      • 2016-07-11
      • 2019-07-17
      • 2018-11-30
      • 2014-08-25
      相关资源
      最近更新 更多