大多数(如果不是所有的话)现代处理器都使用一种称为“分支预测”的技术,它可以猜测在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所指出的,返回值之间的关系以及的
是如何确定的…分支预测器可能不会发挥作用。让我们只考虑true和false作为返回值。对于条件,让我们假设它是一个预定的字段,在函数内部或外部,一个局部变量和一些算术语句。
老实说,我不怀疑条件是局部变量的情况和字段已在同一函数中预定的情况有太大区别。
很可能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
分支预测在条件跳转指令上运行,而不是在高级构造上运行。如果循环条件为真,则使用相同的指令跳回循环顶部。根据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(i52500k)在低功耗时钟速度下,使用默认的cpufreq调控器,在ls
完成之前不会提高时钟速度。
这两个代码示例之间没有区别。else
无关紧要,因为不需要在true子句的末尾分支。即使这不是真的,true子句末尾的分支也不会是有条件的。
换句话说,代码必须编译为如下内容:
Compute test expression
Branch if false to false_label
True action
Return some value
False_label;
False action
Return some other value