大多数通用处理器在分支预测错误时会刷新流水线。条件分支的负面性能影响已经促进了急切执行(其中两个路径都被执行,然后选择正确的路径)和动态预测(其中分支阴影中的指令是有条件的)的提议,以及对分支预测(以及其他技术)的广泛研究。(
Mark Smotherman关于急切执行的页面提供了一些详细信息和参考资料。我还想添加Hyesoon Kim等人的“Wish Branches:结合条件分支和预测进行自适应预测执行”,2005年,作为一篇重要论文。)
IBM的POWER7似乎是第一个实现比预取替代路径更复杂的主流处理器(即,急切获取),而且它只处理单个指令情况。(POWER7使用分支预测置信度估计来选择是否预测或使用预测。)
急切执行存在明显的资源消耗问题。即使基于分支预测置信度、推测深度和资源可用性(前端可用信息)进行选择性急切,沿着单一路径更深层次的推测往往更加有效。发现多条路径的连接点并避免过多的冗余计算也会增加复杂性。(理想情况下,控制独立操作只会执行一次,并且连接和数据流会被优化,但这样的优化会增加复杂性。)
对于一个深度流水线的顺序处理器而言,预测短向前分支不被采纳,并仅在实际采取该分支时将流水线中向后清空到被该分支指向的指令可能看起来是有吸引力的。如果每次只允许一个这样的分支进入流水线(其他分支使用预测),则为每个指令添加一个位可以控制其是否转换为nop或执行。(如果仅处理跨越单个指令的情况,则允许流水线中存在多个分支可能并不特别复杂。)
这类似于在采取分支时废除的分支延迟槽。MIPS具有“分支可能”指令,如果未采取则作废,这些指令在修订2.62中被标记为过时。虽然其中一些理由可能是为了将实现与接口分离以及希望恢复指令编码空间,但这个决定也暗示着该概念存在一些问题。
如果对所有短向前分支都这样做,当分支正确预测为已执行时,它将会丢弃指令。(请注意,如果已执行的分支总是经历提取重定向的延迟,这在深度流水线处理器中更有可能出现,那么这种惩罚可能会减少,此时按照没有分支提取与正确预测的执行分支具有相同的性能。然而,可以认为处理器特殊处理这样的短执行分支,以最小化此类提取气泡。)
作为一个例子,考虑一个标量流水线(每个周期的非分支指令等于1.0),其中分支决议在第八阶段结束,对于正确预测的转移分支没有取指重定向惩罚,处理单条指令的跨越分支。假设这样的短前向分支(占指令的2%,被执行30%)的分支预测器准确率为75%(不受方向影响),其他分支(占指令的18%)的准确率为93%。如果将短分支错误地预测为“已执行”,可以节省8个周期(这样的分支有17.5%会被错误地预测为已执行,占指令的0.35%),如果将其错误地预测为“未执行”,可以节省7个周期(这样的分支有7.2%会被错误地预测为未执行,占指令的0.144%),如果正确地预测为“已执行”,则会损失1个周期(22.5%,占指令的0.45%)。总体而言,每条指令可节省0.03358个周期。如果没有这种优化,每条指令需要1.2758个周期。
尽管上述数字仅为示例,但它们可能与现实并不远,除了非分支指令的1.0 IPC。提供小型循环缓存将减少错误预测的惩罚(并在短循环中节省功率),因为指令缓存访问可能是八个周期中的三个。添加缓存未命中的影响将进一步降低此分支优化的百分比改善。避免针对预测“强烈采取”的短分支的开销可能是值得的。
为了设计,倾向于使用较窄和较浅的流水线,并偏爱简单性(以降低设计、功率和面积成本)。由于指令集可能支持许多短分支情况下的无分支代码,因此优化这个方面的激励进一步降低。
对于乱序实现,可能会对潜在分支的指令进行预测,因为处理器希望能够执行后续的非相关指令。预测引入了额外的数据依赖性,必须检查调度。指令调度器通常仅提供每个指令两个比较器,并拆分条件移动(一个只有三个数据流操作数的简单指令:旧值、备用值和条件;一个受谓词控制的寄存器-寄存器加法将有四个操作数。(有其他解决此问题的方法,但本答案已经很长了。)
一种乱序实现也不会在分支条件不可用时停滞。这是控制依赖和数据依赖之间的权衡。通过准确的分支预测,控制依赖非常廉价,但数据依赖可能会阻碍前进等待数据操作数。(当然,对于布尔数据依赖,值预测变得更具吸引力。在某些情况下,使用谓词预测可能是可取的,并且相对于简单的谓词化,它具有使用动态成本和置信度估计的优势。)
(或许ARM选择在64位AArch64中放弃广泛的谓词化,说明了一些问题。虽然这在指令编码方面占据很大部分,但谓词化对于高性能实现的好处显然相对较低。)
编译器问题
分支代码和无分支代码的性能取决于分支的可预测性和其他因素(包括如果采用,重定向提取的任何惩罚),但编译器很难确定分支的可预测性。即使是配置文件数据通常只提供分支频率,这可能会给出一种悲观的可预测性观点,因为这样不考虑分支预测器使用本地或全局历史记录。编译器也不完全了解数据可用性和其他动态方面的时间。如果条件比用于计算的操作数稍后可用,则将控制依赖(分支预测)替换为数据依赖(预测)可能会降低性能。无分支代码还可能引入更多活跃值,可能会增加寄存器溢出和填充开销。
更进一步的是,大多数仅提供条件移动或选择指令的指令集不提供条件存储。虽然可以通过使用条件移动来选择一个安全的、被忽略的存储位置来解决这个问题,但这似乎是一个不太理想的复杂化。此外,条件移动指令通常比简单的算术指令更昂贵;一个加法和条件移动可能需要三个周期,而正确预测的分支和加法将需要零个周期(如果加法被分支覆盖)或一个周期。
另一个复杂性在于,条件操作通常被分支预测器忽略。如果后续的保留分支与移除分支的条件相关,则该后续分支的分支错误率可能会增加。(可以使用谓词预测来保留这些已移除分支的预测效果。)
随着对矢量化的重视增加,无分支代码的使用变得更加重要,因为基于分支的代码限制了对整个向量进行操作的能力。