performance - 即使对于很短的if语句主体,分支预测错误也会刷新整个管道吗?

标签 performance x86 branch cpu-architecture branch-prediction

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

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

那么,现代的CPU是否有任何机制可以仅丢弃短if体内的少量指令?还是真的冲洗了整个管道?如果是后者,那么我想使用条件移动指令将获得更好的性能。顺便说一句,有谁知道现代编译器是否擅长将简短的if语句转换为cmov指令?

最佳答案

大多数通用处理器确实会在分支预测错误的情况下刷新管道。除了对分支预测的广泛研究(以及对分支预测的广泛研究)以外,条件分支对性能的负面影响还激发了积极执行的建议(执行两条路径并随后选择正确的路径)和动态预测(确定分支阴影中的指令)。和其他技术一样)。 (Mark Smotherman's page on eager execution提供了一些详细信息和参考。我将Hyesoon Kim等人的“ Wish Branches:结合条件分支和谓词用于自适应谓词执行”(2005年)作为重要论文。)

IBM的POWER7似乎是第一个实现比预取备用路径(即急取)更复杂的功能的主流处理器,并且仅处理单个指令情况。 (POWER7使用分支预测置信度估计来选择是谓词还是使用预测。)

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

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

这将类似于如果被采取的分支延迟时隙。 MIPS具有“可能分支”的指令,如果不采取,则将其废止,并且在修订版2.62中将其标记为已过时。尽管这样做的某些理由可能是将实现与接口分离,以及恢复指令编码空间的愿望,但这一决定也暗示了该概念存在一些问题。

如果对所有短前向分支都执行了此操作,则当正确预测分支被采用时,它将丢弃指令。 (请注意,如果采用的分支始终遇到提取重定向延迟,则这种惩罚可能会更少,这在深度流水线处理器中的多周期指令高速缓存访​​问中更有可能发生。在这种情况下,就像没有分支一样进行获取具有与正确预测的采用分支相同的性能。但是,有人可能会认为处理器的特殊情况是采用如此短的采用分支以最大程度地减少此类获取气泡。)

例如,考虑一个标量流水线(每个周期的非分支指令等于1.0),在第八阶段末尾具有分支分辨率,并且在正确预测的已采用分支上没有取回重定向损失,可以处理单指令分支转换。假设对于这样的短前向分支(指令的2%,占30%的时间),分支预测器的准确度为75%(无方向偏差),而对于其他分支的预测精度为18%,则为93%。对于被错误预测为已使用的短分支(此类分支的17.5%;指令的0.35%),将保存八个周期;如果被错误预测为未采用的短分支将被保存七个周期(7.2%; 0.144%);如果正确,则将丢失一个周期预测为接受(22.5%; 0.45%)。每条指令总共可以节省0.03358个周期。没有这种优化,每条指令的周期将是1.2758。

(虽然上述数字仅作为示例,但它们可能与非分支指令的1.0 IPC相距不远。提供较小的循环高速缓存将减少错误预测的代价(并在短循环中节省功耗),因为可以访问指令高速缓存可能是八个周期中的三个周期。增加缓存未命中的影响将进一步降低此分支优化的百分比。避免预测“强烈采用”的短分支的开销可能是值得的。)

为了使设计趋向于使用较窄和较浅的管线,并倾向于简化(以降低设计,功耗和面积成本)。由于指令集可能在许多短分支情况下都支持无分支代码,因此优化此方面的动机就进一步降低了。

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

当分支条件不可用时,乱序实现也不会停止。这是控件依赖项和数据依赖项之间的折衷。使用精确的分支预测,控制依赖关系极其便宜,但是数据依赖关系可以阻止等待数据操作数的正向进度。 (当然,对于布尔数据依赖性,值预测变得更具吸引力。在某些情况下使用谓词预测可能是合乎需要的,并且比使用动态成本和置信度估计的简单谓词更具优势。)

(也许这说明ARM选择了在64位AArch64中放弃广泛的谓词。尽管其中很大一部分是用于指令编码,但高性能实现的谓词优势可能相对较低。)

编译器问题

无分支代码与分支代码的性能取决于分支的可预测性和其他因素(包括(如果采取的话,将对重定向获取造成任何损失)),但是编译器很难确定分支的可预测性。即使简档数据通常也仅提供分支频率,这可以给出悲观的可预测性视图,因为这不能说明使用局部或全局历史的分支预测器。编译器也不是很清楚数据可用性和其他动态方面的时机。如果条件晚于用于计算的操作数可用,则用数据依赖关系(预测)替换控制依赖关系(分支预测)可能会降低性能。无分支代码还可能引入更多的实时值,从而潜在地增加寄存器溢出和填充开销。

更为复杂的是,大多数仅提供条件移动或选择指令的指令集不提供条件存储。尽管可以通过有条件的移动来选择安全的,被忽略的存储位置来解决此问题,但这似乎没有什么吸引力。另外,条件移动指令通常比简单的算术指令更昂贵。加法和条件移动可能需要三个周期,而正确预测的分支和加法将花费零(如果加法分支了)或一个周期。

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

随着对向量化的日益重视,由于基于分支的代码限制了在整个向量上使用运算的能力,因此无分支代码的使用变得更加重要。

关于performance - 即使对于很短的if语句主体,分支预测错误也会刷新整个管道吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29522431/

相关文章:

assembly - Linux内核0.01引导加载程序使用rep movw,而不是rep movsw?这实际上是重复普通的 MOV 吗?

C++ 设置堆栈指针

merge - 我可以在化石的树干上做一个替代分支吗

c - 跳台 - 组件 8086

sql - 在 SQL 中存储/更新基于 Interval 的数据的最有效方法是什么?

python - 为什么这种复制列表的方法比其他方法快得多?

objective-c - NSString(或字符串)的长度是否影响 isEqualToString 的性能 : (or ==)?

c# - 使用 .net 为 x64 显式编译是否有意义?

mercurial - 使用 Mercurial 本地克隆进行分支开发?

c - c 中的快速 I/O,stdin/out