我读到一个完美预测的分支有零/几乎为零的开销。 (例如:https://stackoverflow.com/a/289860/8038490)我不太明白人们的意思。至少必须评估分支条件,这可能是一个简单的bool或函数调用,需要时间。
答案 0 :(得分:3)
评估分支条件总是需要一些工作,即使完美预测,但由于现代CPU中的内部并行性,额外的工作没有必要添加到特定指令序列的成本。
我认为部分困惑在于许多人对执行CPU指令的心理表现模型。是的,每条指令都需要一些工作,所以这意味着每条指令都有一些成本,无论多么小,在执行时测量,对吗?
如果执行的总成本只是每项指令的工作中的附加值,那就是真的 - 您只需将所有工作加在一起并获得最终的成本 。由于现代CPU中的并行性很大,它并不像那样工作。
想想组织一个生日聚会。您可能需要购买需要10分钟的面粉,然后烤一个需要60分钟的蛋糕,然后去拿30分钟的特殊礼物。那些时间都是"工作"活动所需。然而,有人可以拿起礼物,同时面粉被拿起,蛋糕正在被烘烤。但是,你不能在没有面粉的情况下烤蛋糕。所以你有两个依赖链:70分钟买面粉 - >烤蛋糕链,以及30分钟的皮卡礼品链。通过无限制的并行性,只有70分钟的蛋糕相关链有助于实现一切准备就绪的时间。拿起礼物30分钟的工作,但由于其他需要更长时间的工作(也就是关键的),它最终花费没有时间(不延迟完成所有任务)路径)并行发生。
可以并行执行更多额外任务,直到您没有人员分配给他们。 (此时,执行吞吐量限制开始增加延迟,这称为资源冲突。如果资源冲突延迟关键路径,而不是较短的依赖链之一.CPU不知道哪个依赖链是/将是关键路径,因此他们的日程安排并不像智能人类在此计划类比中那样优先考虑。)
有关这些内容如何直接应用于CPU的不太抽象和实用的看法,请参阅A Whirlwind Introduction to Dataflow Graphs。
一旦我们有了这个新的心智模型,其中指令序列的成本通常由序列中的某个关键路径支配,我们就可以开始看到为什么预测良好的分支通常非常低或零成本:
这些因素结合起来使大多数预测的分支指令成本为零或成本几乎为零。
你不必接受我的话,让我们看一个真实的例子:
int mul1(int count, int x) {
do {
x *= 111;
} while (--count);
return x;
}
给定count
和起始值x
,它会将x
乘以111 count
次并返回结果。循环assembles到3个指令一个用于乘法,一个用于--count
,一个分支用于检查count
值:
.L2:
imul eax, eax, 111
sub edi, 1
jne .L2
现在这里是相同的循环,但有一个额外的分支:
int mul2(int count, int x) {
do {
x *= 111;
if (x == 0) {
abort();
}
} while (--count);
return x;
}
这assembles到5条指令。额外的两个用于x
的测试,测试表明x
为零:
.L7:
imul eax, eax, 111
test eax, eax
je .L12 ; ends up calling abort
sub edi, 1
jne .L7
那么增加60%的指令(包括分支机构)的成本是多少?零,至少为4位有效数字 3 :
Running benchmarks groups using timer libpfc
** Running benchmark group stackoverflow tests **
Benchmark Cycles
No branch 3.000
Added test-branch 3.000
每次迭代需要3个周期,因为它受到涉及3个周期乘法的依赖链的限制。附加指令和分支没有花费任何成本,因为它们没有添加到此依赖关系链并且能够执行" out of line",隐藏在关键路径的延迟之后。 / p>
1 从概念上讲,分支指令会写出" rip"注册,但这并不像其他寄存器那样对待:它的进展是提前预测的,因此依赖性被预测器打破。
2 当然,首先还有额外的工作来解码和融合指令,但这通常不是瓶颈,所以可能是" free"在成本方面,像uop缓存这样的东西意味着甚至可能不经常执行。此外,在x86上,虽然融合分支指令具有与ALU操作相同的延迟,但它在可以执行的端口方面不太灵活,因此根据端口压力,融合指令可能会有一些成本与裸ALU相比。
3 事实上,如果你去"无限"有效数字并查看原始循环计数,您会发现在这两种情况下,此循环的其他迭代成本完全 3个循环。无分支情况通常总共缩短1个周期(随着迭代次数的增加,相对意义上的差异变为0),可能是因为初始非稳态迭代需要额外的周期,或者错误预测恢复需要最后一次迭代的另一个循环。
答案 1 :(得分:2)
分支预测是在指令级别预测条件的结果,这是C或C ++条件下的实际结果 - 如果这是百万字符串比较的结果,它可能不是特别有用,因为这种比较是要花很多时间。如果它是for循环的结束条件,它遍历两个字符串,每个字符串有一百万个字符,那么它非常有用,因为它在该循环中多次出现(假设字符串相等)。
不能自由地对两个长字符串进行字符串比较。可以自由地猜测字符串比较将继续(直到我们找到字符串的结尾或差异,此时分支预测出错)。
“不可预测的”分支将导致处理器不知道代码的继续位置。现代CPU具有相当长的流水线(15-30步),因此如果流水线没有填充“正确”的代码,处理器将不得不等待“正确”的代码流过管道。
所以回答实际的问题:
当分支本身被很好地预测时,处理器已经在管道中获得了RIGTH指令,并且在我们可以执行正确的指令以继续该程序之前没有“管道泡沫”来遍历管道。请参阅下面的类比。如果预测是错误的,那么管道中将存在除正确指令之外的其他内容,并且处理器必须通过这些指令进行咀嚼,将它们扔掉。
将其视为汽车制造厂,制造A型和B型车型,在生产线上首先将车身安装到底盘上,进行喷涂(魔术涂料,几乎立即干燥),然后适合发动机和变速箱,然后把轮子放在上面,适合灯光,最后装上玻璃,这是一辆完整的汽车。每个步骤需要20分钟才能完成,并且汽车的传送带将每20分钟向前移动到下一个位置[对于本练习,我们忽略了移动本身需要时间的事实]。
您负责生产线,生产线上有一堆A车。突然间,这位大老板说,“我们刚收到B车的订单,立即转向制作B型车”。因此,您开始将B汽车零件送入生产线。但是在下一辆B车出现在另一端之前还需要一段时间。
分支预测的工作方式是“猜测”代码是改变方向还是转到下一条指令。如果它猜对了,那就像是猜测“大老板”是什么时候告诉你要改变A和B型号的汽车,所以你可以让老车准备好在老板想要的时候从生产线上弹出,而不是必须等待整条生产线
当它工作时,它很棒,因为预期的东西已经准备好了。如果您猜错了,您仍然需要等待生产线的其余部分运行当前设置,并将其存入“我们没有这些客户的角落”(或根据CPU指令) “放弃指示”)。
大多数现代CPU也允许“投机执行”。这意味着处理器将在实际确定条件之前开始执行指令。因此,在老板说出来之前,处理器将从A车换到B车上工作。如果在那时,老板说“不,你应该继续开车”,你有一堆车要丢弃,你已经开始工作了。你不一定要建造所有的汽车,但你必须通过生产线,每20分钟一步。