分支预测:避免"否则"简单操作的分支使代码更快(Java示例)?

时间:2015-02-04 09:16:08

标签: java assembly compiler-construction branch-prediction vm-implementation

选项1:

  boolean isFirst = true;
  for (CardType cardType : cardTypes) {
    if (!isFirst) {
      descriptionBuilder.append(" or ");
    } else {
      isFirst = false;
    }
    //other code not relevant to this theoretical question
  }

选项2:

boolean isFirst = true;
for (CardType cardType : cardTypes) {
  if (!isFirst) {
    descriptionBuilder.append(" or ");
  } 
  isFirst = false;
  //other code not relevant to this theoretical question
}

我的分析:两个代码都有相同的语义。

第一个代码)我不确定这个代码是否有两个分支(就分支预测器而言)或一个分支。我正在调查http://en.wikipedia.org/wiki/X86_instruction_listings但是无法弄清楚有一个X86指令类似于"如果之前的条件值是false跳转那么",以避免两个分支预测(非常糟糕)

第二代码)最有可能总是执行简单的MOV(注册或很可能已经在缓存中的元素),这相对便宜(最多几个周期)

所以,我的意见是,除非处理器解码成微码指令可以做一些智能或X86指令存在以避免必要的分支预测,第二代码更快。

我理解这纯粹是理论上的问题,因为在实践中,这个分支可以使应用程序快0.000000002%或类似的东西。

我错过了什么吗?

编辑:我添加了一个循环,用于提供更多"体重"分支问题

EDIT2:问题是关于分支预测的英特尔架构(奔腾和更新的处理器)。

6 个答案:

答案 0 :(得分:3)

使用 JMH 给出以下数字,其大小为 10 的 cardTypes 数组和整数增量作为逻辑(Java 15 / AMD 3950X / Windows 10):

Benchmark          Mode  Cnt          Score         Error  Units
Benchmark.option1  thrpt   25  273369417.720 ± 1618952.179  ops/s
Benchmark.option2  thrpt   25  273415784.192 ±  852618.585  ops/s

“选项 2”的平均性能提高了约 0.017% (YMMV)。

另见:branch predictionmethod dispatchmemory accessthroughput and latencygarbage collection

答案 1 :(得分:2)

代码具有相同的效果,但不会生成相同的字节代码或程序集(可能)。

这在性能方面有多大差异,目前尚不清楚,而且可能微不足道。

什么是远,更重要的是代码的清晰度。我看到了更多的错误和性能问题,因为在这样的简单情况下代码难以推理。

简而言之,对您来说最清楚和最简单的事情也可能足够快,或者最容易修复。

答案 2 :(得分:2)

不同的硬件对每个汇编指令都有不同的成本,而在现代硬件上,由于流水线和缓存的影响,甚至难以预测指令的成本。

在您的隔离示例中,if和if / else on pipelining和caches之间的区别并不明确。如果您运行该代码一次,则根本不会发现任何差异。在紧密的循环中反复运行它,if本身的性能将由a)支票的成本和b)支票结果的可预测性决定。换句话说,分支预测将成为主导因素,并且不会受到if或if / else代码块的影响。

关于分支预测效果的优秀讨论可以在这里阅读Why is it faster to process a sorted array than an unsorted array?(参见最高得分答案)。

答案 3 :(得分:0)

  

两个代码都具有相同的语义。

否两个代码都不同,

如果条件isFirst = false;不匹配,首先将代码应用if (!isFirst)设置为false。

每次将标志更改为false,即使条件满足或不满足,也会使用第二个代码。

答案 4 :(得分:0)

if/else构造中有两个分支:顶部的条件分支,以及else部分末尾的if部分周围的分支。 else部分中没有分支,至少在任何中等程度上没有实现的编译器中都没有分支。

相反,你必须平衡总是执行isFirst = false;行的费用。

在你提到的具体情况中,与方法调用的成本相比,它不太可能产生丝毫差异。

答案 5 :(得分:0)

假设您的代码段是for循环中的if块。 Hotspot具有展开for循环的能力,这包括采用常见的'循环的第一次迭代'检查并在循环外部内联它。从而避免了在循环的每次迭代中重新检查条件的成本。因此避免关注更快,if或else。

Oracle记录了此行为here