分支错误预测是否会冲洗整个管道,即使是非常短的if语句主体?

时间:2015-04-08 18:22:15

标签: performance x86 branch cpu-architecture branch-prediction

我读过的所有内容似乎都表明分支错误预测总会导致整个管道被刷新,这意味着浪费了很多周期。我从来没有听到任何人提到短期if条件的任何例外情况。

在某些情况下,这似乎真的很浪费。例如,假设您有一个单独的if语句,其中包含一个非常简单的主体,该主体被编译为1个CPU指令。 if子句将被编译为一条指令的条件跳转。如果CPU预测分支不被采用,则它将开始执行if-body指令,并可立即开始执行以下指令。现在,一旦if条件的评估已经到达管道的末端,也就是说,12个周期之后,CPU现在知道它的预测是对还是错。如果它被错误预测,并且分支实际被占用,则CPU实际上只需要丢弃来自管道的1条指令(if-body中的指令)。但是,如果它刷新整个管道,那么在以下指令中完成的所有工作也都被浪费了,并且必须无缘无故地重复。在深度流水线化的架构上浪费了很多周期。

现代CPU有没有机制只丢弃短if体内的少数指令?或者它真的冲洗整个管道?如果它是后者,那么我认为使用条件移动指令会获得更好的性能。顺便说一下,有没有人知道现代编译器是否善于将短if语句转换为cmov指令?

4 个答案:

答案 0 :(得分:9)

大多数通用处理器会在分支错误预测上冲洗管道。除了对分支预测进行广泛研究之外,条件分支的负面性能影响推动了热切执行的建议(其中两条路径都被执行并且后面选择了正确的路径)和动态预测(分支阴影中的指令被预测)的建议(以及作为其他技术)。 (Mark Smotherman's page on eager execution提供了一些细节和参考资料。我将添加Hyesoon Kim等人的希望分支:将条件分支和预测结合起来进行自适应预测执行",2005,作为一个重要的纸。)

IBM的POWER7似乎是第一个实现比预取备用路径更复杂的主流处理器(即,急切获取),它只处理单指令情况。 (POWER7使用分支预测置信度估计来选择是否预测或使用预测。)

急切执行有明显的资源使用爆炸性问题。即使基于分支预测置信度,投机深度和资源可用性(前端可用信息)的选择性渴望,也可以更加有效地推测单个路径的更深层次。发现多个路径的连接点并避免过多的冗余计算也会增加复杂性。 (理想情况下,控制独立操作只会执行一次,并且会优化连接和数据流,但这种优化会增加复杂性。)

对于深度流水线的有序处理器,预测短前向分支未被采用并且仅在实际采用分支时仅在管道中向后刷新到所采用分支所针对的指令可能看起来很有吸引力。如果一次只允许一个这样的分支在管道中(其他分支使用预测),则向每个指令添加单个位可以控制它是转换为nop还是执行。 (如果仅处理单个指令被分支的情况,则允许管道中的多个分支可能不是特别复杂。)

这类似于annul-if-taken分支延迟时隙。 MIPS已经"分支可能"如果,则取消的说明,并在修订版2.62中标记为已过时。虽然这样做的一些理由可能是将实现与接口分开以及恢复指令编码空间的愿望,但这一决定也暗示该概念存在一些问题。

如果对所有短前向分支进行了此操作,则在正确预测分支时将丢弃指令。 (请注意,如果采用的分支始终在获取重定向中遇到延迟,这种惩罚可能会更少,这在深度流水线处理器中进行多周期指令高速缓存访​​问更有可能。在这种情况下,读取就好像没有分支一样具有与正确预测的采用分支相同的性能。然而,有人可能会争辩说处理器特殊情况如此短的分支以最小化这种获取气泡。)

作为一个例子,考虑一个标量流水线(每个周期的非分支指令等于1.0),在第八阶段结束时具有分支分辨率,并且在正确预测的采用分支上没有获取重定向惩罚,处理单指令分支。对于这样的短前向分支(占指令的2%,占30%的时间)和其他分支(占指令的18%)的准确率为93%,假设分支预测准确度为75%(不受方向影响)。对于短分支将保存8个周期,这些分支将被错误预测(17.5%的此类分支; 0.35%的指令),7个周期,如果错误预测未采取(7.2%; 0.144%),并且正确的一个周期将丢失预测为(22.5%; 0.45%)。每条指令总共可保存0.03358个周期。如果没有这种优化,每条指令的周期将为1.2758。

(虽然上面的数字仅仅是例如,它们可能与现实相差不远,除了非IPC分支指令的1.0 IPC。提供一个小的循环缓存可以减少错误预测惩罚(并在短循环中节省功耗),因为指令缓存访问可能是八个周期中的三个。添加缓存未命中的影响将进一步降低此分支优化的百分比改善。避免预测的&#34;强烈采用的&#34;短分支的开销可能< / em>值得。)

为了使设计倾向于使用窄而浅的管道并且更喜欢简单(为了更低的设计,功率和面积成本)。由于指令集很可能支持许多短分支情况下的无分支代码,因此优化这一方面的动机会进一步减少。

对于无序实现,由于处理器希望能够执行稍后的非依赖指令,因此必须预测可能分支的指令。预测引入了额外的数据依赖性,必须对其进行检查以进行调度。指令调度程序通常每条指令只提供两个比较器并分割条件移动(只有三个数据流操作数的简单指令:旧值,替代值和条件;谓词寄存器寄存器添加将具有四个操作数。(有其他方法可以解决这个问题,但这个答案已经很长了。)

当分支条件不可用时,无序实现也不会停止。这是控制依赖性和数据依赖性之间的权衡。通过精确的分支预测,控制依赖性非常便宜,但是数据依赖性可以阻止等待数据操作数的前进进程。 (当然,对于布尔数据依赖性,值预测变得更具吸引力。在某些情况下使用谓词预测可能是理想的,并且优于使用动态成本和置信度估计的简单预测。)

(或许可以说ARM选择在64位AArch64中放弃大量预测。虽然其中很大一部分用于指令编码,但预测高性能实现的好处可能相对较低。)

编译器问题

无分支代码与分支代码的性能取决于分支的可预测性和其他因素(包括,如果采用重定向提取的任何代价),但编译器很难确定分支的可预测性。甚至简档数据通常仅提供分支频率,其可以给出对可预测性的悲观看法,因为这不考虑使用本地或全局历史的分支预测器。编译器也不完全了解数据可用性的时间和其他动态方面。如果条件晚于用于计算的操作数,则用数据依赖性(预测)替换控制依赖性(分支预测)会降低性能。无分支代码也可能引入更多实时值,可能会增加寄存器溢出和填充开销。

进一步复杂化,大多数仅提供条件移动或选择指令的指令集不提供条件存储。虽然这可以通过使用条件移动来选择安全的,被忽略的存储位置来解决,但这似乎是一种没有吸引力的复杂性。另外,条件移动指令通常比简单算术指令更昂贵;加法和条件移动可能需要三个周期,其中正确预测的分支和加法将取零(如果加法分支)或一个周期。

进一步的复杂性是分支预测器通常忽略谓词操作。如果稍后保留的分支与移除的分支的条件相关,则对于该后一分支,分支错误预测率可能增加。 (谓词预测可用于保留此类已删除分支的预测效果。)

随着对矢量化的日益重视,无分支代码的使用变得更加重要,因为基于分支的代码限制了对整个矢量使用操作的能力。

答案 1 :(得分:4)

现代高性能无序CPU通常不会刷新整个管道 0 :它们通常使用类似于刷新分支指令和所有较年轻指令的策略< / em>的。刷新前端,这将充满错误预测路径上的指令,但超出前端的现代核心可能同时有超过100个指令在飞行中,其中只有一些可能比分支更年轻。

这意味着分支的成本至少部分与周围指令相关:如果可以早期检查分支条件错误预测的影响可能会受到限制甚至为零< SUP> 1 。另一方面,如果分支条件处理较晚,在错误的路径上花费了大量资源后,成本可能会很高(例如,大于12-20周期“已发布”的分支误预测惩罚,您经常会看到)。

0 确切的术语在这里有争议:刷新管道的含义对于无序处理器来说并不完全清楚。这里我的意思是CPU不会刷新所有正在运行但可能未执行的指令。

1 特别是,某些指令序列的限制因素可能是一个依赖链,其当前执行远远超出指令窗口的前沿,错误预测不会刷新任何这些说明并没有减慢代码的速度。

答案 2 :(得分:3)

&#34;如果它被错误预测,并且实际上是分支,那么CPU  实际上只需丢弃来自管道的1条指令(if-body中的指令)。&#34;

这并不像你说的那么容易。指令修改其他指令所依赖的架构中的各种不同状态(寄存器结果,条件标志,存储器等)。当您意识到自己错误预测时,您可能会在管道中有大量指令,这些指令已根据该指令更改的状态以及管道中的所有后续指令开始执行...更不用说指令了可以提出错误/例外。

一个简单的例子:

b = 0
f (a == 0) {
    b = 1;
}
c = b * 10;
if (b == 0)
    printf("\nc = %d.",c);
foo(b);
etc..

撤消&#34;一条简单的指令&#34;需要做很多工作。

对于预测性差的简单分支,首选预测/ cmovs /等。

答案 3 :(得分:1)

至少在大多数处理器中,错误预测的分支会冲洗整个管道。

这是很多(大多数?)当前处理器也提供谓词指令的很大一部分原因。

在ARM上,大多数指令都是谓词的,这意味着指令本身可以包含一个条件,实质上说,&#34;做X,但只有当以下条件成立时才会这样。&#34;

同样,最近的x86 / x64迭代包括一些预测指令,例如&#34; CMOV&#34; (条件移动)以相同的方式工作 - 只有在满足条件时才执行指令。

这些刷新管道 - 指令本身总是只流经管道。如果条件不满足,则该指令基本上没有任何效果。缺点是指令会占用执行时间,即使它们没有效果。

所以,在你这样的情况下(只有一个小小的身体的if语句)只能在几条指令中实现,你可以将它们作为谓词指令实现。

如果正文采用了足够的指令(大致是指令管道的大小,乘以某个常数因子),那么使用条件跳转就会更有意义。