用于优化循环语句的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程序集,你也可以清楚地看到:除了本地写入调用之外,没有任何调用指令。
- 如何将日期时间从一个时区转换为另一个时区
- Java服务器自签名证书+客户端证书和SSL – 连接重置
- 在java中为图像或图像添加鼠标监听器
- 迁移到Spring 3后,从Eclipse IDE启动基于Maven的GWT App时出现Spring NamespaceHandler问题
- 使用SSLEngine(JSSE)与旧客户端进行SSL握手
- Java 8:Observable List – 在属性更改时调用Invalidation Listener或Change Listener
- 如何获取在java中创建的日期图片
- 新的总是在C ++ / C#/ Java中分配在堆上
- Java – 通过浏览器/ URL连接到ServerSocket