分支预测器是否会启动?

时间:2015-08-11 23:13:37

标签: performance optimization compiler-optimization branch-prediction

大多数(如果不是所有的)现代处理器都使用一种名为" branch prediction"的技术,用它猜测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;
}

分支预测器将预测'第二个例子中的分支,但第一个例子怎么样?会猜到吗?什么将被加载到管道?是否可以通过任何一个示例获得任何速度,而忽略块中实际代码的影响?

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

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

说实话,我并不怀疑条件是局部变量的情况与该字段已在同一函数中预先确定的情况有很大差异。

2 个答案:

答案 0 :(得分:4)

最有可能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对其输入和标志都有数据依赖性。条件分支是控制依赖项。使用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

分支预测对条件跳转指令进行操作,而不是对高级结构进行操作。如果循环条件为真,则使用相同的指令跳回到循环的顶部。根据{{​​3}},最近的英特尔CPU记住了循环最多64次迭代的模式。因此,如果迭代次数为64次或更少,则每次运行相同次数的循环都不会对最后一次迭代进行错误预测。

所以它不是分支预测器查看的指令序列,用于猜测是否采取跳转。每个单独的分支指令在获取时在分支历史缓冲区中获得一个条目。是的,每个编译器别无选择,只能使用jcc(条件代码跳转)指令来实现分支/循环。

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

在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完成之前不会以时钟速度提升。

答案 1 :(得分: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