用于优化循环语句的JVM选项

我在学校被告知,修改for loop的索引变量是一种不好的做法:

示例:

 for(int i = 0 ; i < limit ; i++){ if(something){ i+=2; //bad } if(something){ limit+=2; //bad } } 

争论的焦点是, 某些编译器优化可以优化循环,而不是重新计算索引并在每个循环中绑定。

我已经在java进行了一些测试,似乎默认索引和绑定每次都会重新计算。

我想知道是否可以在JVM HotSpot激活这种function?

例如,要优化这种循环:

 for(int i = 0 ; i < foo.getLength() ; i++){ } 

无需写:

 int length = foo.getLength() for(int i = 0 ; i < length ; i++){ } 

这只是一个例子,我很想尝试看到改进。

编辑

根据Peter Lawrey的回答, 为什么在这个简单的例子中JVM没有内联getLenght()方法?

 public static void main(String[] args) { Too t = new Too(); for(int j=0; j<t.getLength();j++){ } } class Too { int l = 10; public Too() { } public int getLength(){ //System.out.println("test"); return l; } } 

在输出“测试”中打印10次。

我认为优化这种执行可能会很好。

编辑2:似乎我误解了……

我删除了println ,实际上探查器告诉我方法getLength()在这种情况下甚至不调用一次。

我已经在java中进行了一些测试,似乎默认索引和绑定每次都会重新计算。

根据Java语言规范,这个:

 for(int i = 0 ; i < foo.getLength() ; i++){ } 

表示在每次循环迭代时调用getLength() 。 只有Java编译器可以有效地certificate它不会改变可观察行为,才允许getLength()调用移出循环。 (例如,如果getLength()每次只是从同一个变量返回相同的值,那么JIT编译器很可能可以内联调用, 然后推断出它可以进行提升优化。但是如果getLength()涉及获取并发或同步集合的长度,允许优化的可能性很小......因为其他线程可能采取行动。)

这就是允许编译器做的事情。

我想知道是否可以在JVM HotSpot中激活这种function?

简单回答是不。

您似乎建议使用编译器开关来告知/允许编译器忽略JLS规则。 没有这样的开关。 这样的转变将是一个不好的想法 。 这将导致正确/有效/工作程序中断。 考虑一下:

 class Test { int count; int test(String[] arg) { for (int i = 0; i < getLength(arg); i++) { // ... } return count; } int getLength(String[] arg) { count++; return arg.length; } } 

如果允许编译器将getLength(arg)调用移出循环,则会改变调用该方法的次数,从而更改test方法返回的值。

更改正确编写的Java程序行为的Java优化不是有效的优化。 (请注意,multithreading往往会使水变得混乱.JLS,特别是内存模型规则,允许编译器执行优化,这可能导致不同的线程看到应用程序状态的不一致版本...如果它们不同步从开发人员的角度来看,导致行为是不正确的。但真正的问题在于应用程序,而不是编译器。)


顺便说一句,一个更令人信服的理由是你不应该更改循环体中的循环变量,这会让你的代码更难理解。

这取决于foo.getLength()的作用。 如果它可以内联,它可以是有效的相同的东西。 如果无法内联,则JVM无法确定结果是否相同。

顺便说一句,你可以写一个class轮。

 for(int i = 0, length = foo.getLength(); i < length; i++){ } 

编辑:没有价值;

  • 方法和循环通常不会被优化,直到它们被调用10,000次。
  • 分析器子样本调用以减少开销。 他们可能会计算每10或100或更多,所以一个微不足道的例子可能不会出现。

不这样做的主要原因是它使得理解和维护代码变得更加困难。

无论JVM如何优化,都不会损害程序的正确性。 如果由于索引在循环内被修改而无法进行优化,那么它将不会对其进行优化。 我没有看到Java测试如何显示是否存在这样的优化。

无论如何,Hotspot会为你优化很多东西。 而你的第二个例子是Hotspot很乐意为你做的一种明确的优化。

在我们进行更多推理之前, 为什么没有内联访问。 也许我们应该表明是的,如果你知道你在寻找什么(这在Java中真的是非常重要的),那么字段访问内联就好了。

首先,我们需要对JIT如何工作有一个基本的了解 – 我真的不能在一个答案中这样做。 可以说JIT仅在经常调用函数后才起作用(通常> 10k)

所以我们使用以下代码进行实际测试:

 public class Test { private int length; public Test() { length = 10000; } public static void main(String[] args) { for (int i = 0; i < 14000; i++) { foo(); } } public static void foo() { Test bar = new Test(); int sum = 0; for (int i = 0; i < bar.getLength(); i++) { sum += i; } System.out.println(sum); } public int getLength() { System.out.print("_"); return length; } } 

现在我们编译这段代码并用java.exe -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Test.foo Test >Test.txt运行它java.exe -XX:+UnlockDiagnosticVMOptions -XX:CompileCommand=print,*Test.foo Test >Test.txt这导致了一个邪恶的长输出,但有趣的部分是:

  0x023de0e7: mov %esi,0x24(%esp) 0x023de0eb: mov %edi,0x28(%esp) 0x023de0ef: mov $0x38fba220,%edx ; {oop(a 'java/lang/Class' = 'java/lang/System')} 0x023de0f4: mov 0x6c(%edx),%ecx ;*getstatic out ; - Test::getLength@0 (line 24) ; - Test::foo@14 (line 17) 0x023de0f7: cmp (%ecx),%eax ;*invokevirtual print ; - Test::getLength@5 (line 24) ; - Test::foo@14 (line 17) ; implicit exception: dispatches to 0x023de29b 0x023de0f9: mov $0x3900e9d0,%edx ;*invokespecial write ; - java.io.PrintStream::print@9 ; - Test::getLength@5 (line 24) ; - Test::foo@14 (line 17) ; {oop("_")} 0x023de0fe: nop 0x023de0ff: call 0x0238d1c0 ; OopMap{[32]=Oop off=132} ;*invokespecial write ; - java.io.PrintStream::print@9 ; - Test::getLength@5 (line 24) ; - Test::foo@14 (line 17) ; {optimized virtual_call} 0x023de104: mov 0x20(%esp),%eax 0x023de108: mov 0x8(%eax),%ecx ;*getfield length ; - Test::getLength@9 (line 25) ; - Test::foo@14 (line 17) 0x023de10b: mov 0x24(%esp),%esi 0x023de10f: cmp %ecx,%esi 0x023de111: jl 0x023de0d8 ;*if_icmpge ; - Test::foo@17 (line 17) 

这是我们实际执行的内循环。 请注意,以下0x023de108: mov 0x8(%eax),%ecx将长度值加载到寄存器中 - 它上面的东西用于System.out调用(我已删除它,因为它使它更复杂,但是不止一个人认为这会阻碍内联我把它留在那里)。 即使你不适合x86程序集,你也可以清楚地看到:除了本地写入调用之外,没有任何调用指令。