什么是循环反转技术?


89

我正在阅读一个文档,该文档讨论了Java的即时编译器(JIT)优化技术。其中之一是“循环反转”。文件说:

您将常规while循环替换为do-while循环。而 do-while循环的中设置if条款。这种替换导致更少的两次跳跃。

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

注意: 如果有人可以用Java代码示例进行解释,以及JIT如何将其优化为本地代码以及为什么在现代处理器中是最佳的,那将是很棒的。


2
这不是您要对源代码执行的操作。它发生在本机代码级别。
Marko Topolnik 2013年

2
我知道@MarkoTopolnik。但是我想知道JIT如何在本机代码级别做到这一点。谢谢。
尝试

1
哦,很酷,有一个关于Wikipedia的页面,其中包含大量示例en.wikipedia.org/wiki/Loop_inversion。C示例在Java中同样有效。
本杰明·格伦鲍姆

早前通过的,所以我的问题之一灵感对此进行了这件事情很短的研究,也许结果就会对你有所帮助:stackoverflow.com/questions/16205843/java-loop-efficiency/...
亚当·锡米恩

这与通常将循环条件放在末尾(不管是否执行更少的跳转)是一样的,只是跳转指令更少(每次迭代1 vs 2)吗?
Extremeaxe5

Answers:


108
while (condition) { 
  ... 
}

工作流程:

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

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

工作流程:

  1. 检查条件;
  2. 如果为假,则跳出循环;
  3. 运行一次迭代;
  4. 检查条件;
  5. 如果为true,请跳至步骤3。

比较这两者,您可以轻松地看到,如果循环中只有一个步骤,则后者可能根本不执行任何跳转,并且通常,跳转的次数将比迭代的次数少一个。前者将不得不跳回检查条件,仅当条件为假时才跳出循环。

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

解释上述分支预测:对于每种条件跳转,CPU都有两条指令,每条指令都包含对结果的押注。例如,您将在循环的末尾放置一条指令,说“ 如果不为零则跳转,下注为非零 ”,因为跳转必须在除最后一次迭代之外的所有迭代中进行。这样,CPU开始使用跳转目标后的指令而不是跳转指令后的指令来泵送其管道。

重要的提示

请不要以此为如何在源代码级优化的例子。这已经完全被误导了,因为从您的问题中已经很清楚地知道,从第一种形式到第二种形式的转换是JIT编译器作为例程完全由自己完成的事情。


51
最后的注释确实是非常非常重要的事情。
TJ Crowder 2013年

2
@AdamSiemion:为给定的do-while源代码生成的字节码是无关紧要的,因为我们实际上并没有编写它。我们编写while循环,让编译器和JIT共同(如果需要)(通过循环反转)为我们改进循环。
TJ Crowder 2013年

1
上面的@TJCrowder +1,以及亚当的注释:在考虑JIT编译器的优化时,请不要考虑字节码。字节码更接近Java源代码,而不是实际执行的JIT编译代码。实际上,现代语言的趋势是根本不将字节码作为语言专业化的一部分。
Marko Topolnik 2013年

1
本来可以提供更多信息,但对“ 重要说明”进行了进一步解释。为什么会完全被误导?
arsaKasra 2013年

2
@arsaKasra被误导了,因为通常在源代码中可读性和稳定性胜过优化。尤其是在发现JIT为您做到这一点的过程中,您不应该自己尝试(非常微观的)优化。
Radiodef

24

这样可以优化始终至少执行一次的循环。

然后,常规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);就足够了。
本杰明·格伦鲍姆

别担心。有点使答案的开头行无效。:-)
TJ Crowder 2013年

@TJ嗯,它仍然不会优化未输入的循环,在两种情况下都会有一个跳转。
Keppil 2013年

是的。抱歉,我正在读这意味着您不能将其应用于至少循环不到一次的循环(而不是对他们没有帮助)。现在和你在一起。:-)
TJ Crowder 2013年

@Keppil您可能应该明确指出,在我们有大量迭代X的情况下,我们只会在X迭代之间保存一次跳转。
曼努埃尔·塞尔瓦

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版本中,我们必须跳回到开头,进行测试,然后在发现不正确时跳回到结尾。 。


3

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

该链接提供了循环反转的另一个示例。在少数将递减和比较实现为单个指令集的体系结构中,使用递减和比较操作将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(在第一种情况下是必需的)。这样可以提高性能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.