【问题标题】:What is the loop inversion technique?什么是循环反转技术?
【发布时间】:2014-01-16 13:57:50
【问题描述】:

我正在阅读一份关于 Java 的 just-in-time compiler (JIT) 优化技术的文档。其中之一是“循环反转”。文件说:

您将常规的 while 循环替换为 do-while 循环。和 do-while 循环在 if 子句中设置。这种替换减少了两次跳跃。

循环反转如何工作以及它如何优化我们的代码路径?

注意: 如果有人能用 Java 代码的例子解释一下 JIT 如何将其优化为本机代码以及为什么它在现代处理器中是最佳的,那就太好了。

【问题讨论】:

  • 这不是你会对你的源代码做的事情。它发生在本机代码级别。
  • @MarkoTopolnik 我知道。但我想知道 JIT 在本机代码级别如何做到这一点。谢谢。
  • 哦,酷,有一个关于这个的维基百科页面有很多例子en.wikipedia.org/wiki/Loop_inversion。 C 示例在 Java 中同样有效。
  • 前段时间受SO上的一个问题启发,我对这件事做了一个简短的研究,也许结果会对你有所帮助:stackoverflow.com/questions/16205843/java-loop-efficiency/…
  • 这和循环条件通常放在最后的地方是一样的吗(不管执行的跳转是否会更少),只是为了减少跳转指令(1 vs 2 per迭代)?

标签: java jvm jit machine-instruction


【解决方案1】:

这可以优化一个总是至少执行一次的循环。

一个常规的while 循环将总是至少跳回开始一次,并在结束时跳到结尾一次。一个简单循环运行一次的例子:

int i = 0;
while (i++ < 1) {
    //do something
}  

另一方面,do-while 循环将跳过第一次和最后一次跳转。这是一个与上述循环等效的循环,它可以在没有跳转的情况下运行:

int i = 0;
if (i++ < 1) {
    do {
        //do something
    } while (i++ < 1); 
}

【讨论】:

  • +1 表示正确,首先,请考虑添加代码示例。 boolean b = true; while(b){ b = maybeTrue();}boolean b;do{ b = maybeTrue();}while(b); 之类的东西就足够了。
  • 不用担心。它有点使答案的开头行无效,fwiw。 :-)
  • @T.J.好吧,它仍然不会优化一个没有进入的循环,两种情况都会有一个跳转。
  • 啊,是的。抱歉,我读到这意味着您无法将它应用于至少没有循环一次的循环(而不是它对它们没有帮助)。现在和你在一起。 :-)
  • @Keppil 您可能应该明确指出,在我们有大量迭代 X 的情况下,我们只会在 X 次迭代中保存一次跳转。
【解决方案2】:
while (condition) { 
  ... 
}

工作流程:

  1. 检查条件;
  2. 如果为假,则跳转到循环外;
  3. 运行一次迭代;
  4. 跳到顶部。

if (condition) do {
  ...
} while (condition);

工作流程:

  1. 检查条件;
  2. 如果为假,则跳到循环之外;
  3. 运行一次迭代;
  4. 检查条件;
  5. 如果为真,跳到第 3 步。

比较这两个你可以很容易地看出,后者可能根本不做任何跳转,前提是循环恰好有一步,并且通常跳转的次数会比迭代次数少一。前者要跳回去检查条件,只有在条件为假时才跳出循环。

现代流水线 CPU 架构上的跳转可能会非常昂贵:由于 CPU 在跳转之前完成了检查的执行,因此跳转之后的指令已经在流水线的中间。如果分支预测失败,则必须丢弃所有这些处理。重新启动管道时会延迟进一步执行。

解释上述分支预测:对于每种条件跳转,CPU 都有两条指令,每条指令都包含一个关于结果的赌注。例如,您可以在循环结束时放置一条指令“jump if not zero, betting on not zero”,因为除了最后一次之外的所有迭代都必须进行跳转。这样,CPU 开始使用跳转目标之后的指令而不是跳转指令本身之后的指令来泵送其流水线。

重要提示

不要以此为例说明如何在源代码级别进行优化。这将完全被误导,因为正如您的问题已经清楚的那样,从第一种形式到第二种形式的转换是 JIT 编译器作为例行程序所做的事情,完全独立。

【讨论】:

  • 最后那条注释确实非常非常重要。
  • @AdamSiemion:为给定的do-while 源代码生成的字节码无关紧要,因为我们实际上并没有编写它。我们编写 while 循环,并让编译器和 JIT 在必要时(通过循环反转)为我们共同改进它。
  • @T.J.Crowder +1 表示上述内容,以及给 Adam 的注释:从不在考虑 JIT 编译器的优化时考虑字节码。字节码更接近于 Java 源代码,而不是实际执行的 JIT 编译代码。事实上,现代语言的趋势是根本不将字节码作为语言规范的一部分。
  • 如果对重要说明进行了更多解释,将会提供更多信息。为什么它会完全被误导?
  • @arsaKasra 这是被误导的,因为通常可读性和稳定性胜过源代码中的优化。尤其是 JIT 为您执行此操作的启示,您不应该尝试自己进行(非常微的)优化。
【解决方案3】:

让我们来看看它们:

while 版本:

void foo(int n) {
    while (n < 10) {
       use(n);
       ++n;
    }
    done();
}
  1. 首先我们测试n,如果条件不成立则跳转到done();
  2. 然后我们使用并递增n
  3. 现在我们跳回到条件。
  4. 冲洗,重复。
  5. 当条件不再成立时,我们跳转到done()

do-while 版本:

(请记住,我们实际上并没有在源代码中执行此操作 [这会引入维护问题],编译器/JIT 会为我们执行此操作。)

void foo(int n) {
    if (n < 10) {
        do {
            use(n);
            ++n;
        }
        while (n < 10);
    }
    done();
}
  1. 首先我们测试n,如果条件不成立则跳转到done();
  2. 然后我们使用并递增n
  3. 现在我们测试条件,如果为真则返回。
  4. 冲洗,重复。
  5. 当条件不再成立时,我们流向(不跳转)到done()

例如,如果n 开始是9,我们在do-while 版本中根本不会跳转,而在while 版本中,我们必须跳回到开头,进行测试,然后当我们发现它不是真的时跳回到最后。

【讨论】:

    【解决方案4】:

    循环反转是一种性能优化技术,它可以提高性能,因为处理器可以用更少的指令完成相同的结果。这应该主要提高边界条件下的性能。

    This link 提供了循环反转的另一个示例。在少数将递减和比较实现为单个指令集的架构中,将 for 循环转换为带有递减和比较操作的 while 是有意义的。

    维基百科有一个很好的例子,我在这里再次解释一下。

     int i, a[100];
      i = 0;
      while (i < 100) {
        a[i] = 0;
        i++;
      }
    

    会被编译器转换成

      int i, a[100];
      i = 0;
      if (i < 100) {
        do {
          a[i] = 0;
          i++;
        } while (i < 100);
      }
    

    这如何转化为性能? 当 i 的值为 99 时,处理器不需要执行 GOTO(在第一种情况下需要)。这提高了性能。

    【讨论】:

      猜你喜欢
      • 2021-10-11
      • 2019-12-04
      • 1970-01-01
      • 1970-01-01
      • 2020-03-26
      • 1970-01-01
      • 1970-01-01
      • 2021-11-20
      • 1970-01-01
      相关资源
      最近更新 更多