近有不少人提出Java循环优化问题,问题分为两类:
1)
for(int i=0; i<10000; i--){。。。}
与
for(int i = 100000; i > 0; i--){。。。}
这个比较无非是i++和i—的比较。
2)
for(int i=0; i<1000; i++) {
for(int j=0; j<100000; j++) {
。。。
}
}
与
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 1000; j++) {
。。。
}
}
这个比较主要问题在于循环次数多的放在里面还是外面的问题。
我的观点:首先,这种代码上自以为是的优化是没有意义的;其次,拿C语言的思维来考虑这个问题表示Java基本常识都不懂。
分析:
在具体阐明我的观点前有必要做一点Java常识的普及。(注,若没有特别说明,文中sun的指的是被Oracle收购的那个sun,也用来表示Oracle接手的sun公司的一些产品,如sun的jvm,既指Oracle接手sun后的虚拟机,也指未收购时的sun的虚拟机)
1) jvm。
众所周知,这是java虚拟机。但是,很多人,包括初学者甚至一些工作了的人,对jvm的认识仅仅是sun的hotspot虚拟机,就是从sun官网上下载的那个。
而实际上,sun公司制定的是一个规范,即Java虚拟机规范,搜索jvmspec即可得。同时sun也提供了该规范的一个标准实现,就是sun的hotspot虚拟机(N年前的版本就不说了)。但很多人不知道的是,除了sun实现了虚拟机外,还有很多公司也根据自己的需要实现了Java虚拟机,比较常见的有IBM的J9(Websphere中用的),Oracle的JRockit(Weblogic中用的),Apache的Harmony(由于利益等原因,sun和Oracle都没有给它提供兼容性测试),还有openJDK。这些都是比较流行的。诸如此类,还有很多很多。
2) Java指令集
jvm规范中,为Java定义了一套指令集。如iadd,iinc等,指令集用单字节表示,也就是说不超过255个。
关于规范中jvm指令集最需要注意的一点是:指令集指描述了指令该做什么事情,对于如何去做,是留给jvm实现者自己去思考的,所以不同的jvm实现对于同一段代码在效率上可能会有很大的差别。譬如,对于iadd指令,两个int相加,既可以直接交给硬件去做,也可以拐弯抹角的去做,只要最终结果符合jvm规范的描述即可。
3) Java栈
需要知道的是,Java考虑到跨平台的需要,所有指令的操作都不是基于寄存器的,而是内存中的Java栈。在C语言中,有个寄存器用于pc计数器,而在Java中,pc计数器是内存中的一个字。Java栈由栈帧组成,栈帧分为局部变量区,操作数栈和帧数据区。jvm指令的操作数大都源于操作数栈。这与一些语言从寄存器中取操作数是不同的。而局部变量区和操作数栈的大小在编译Java文件时就已经确定了。
对于问题一,我们有必要看一下Java中对于i++和i—所使用的指令
public class Test {
public static void main(String... args) {
int i = 0;
i++;
i--;
}
}
上面的代码编译后再用javap –c Test查看用到的指令:
Compiled from "Test.java"
public class Test extends java.lang.Object{
public Test();
Code:
0: aload_0
1: invokespecial #1; //Methodjava/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iinc 1, 1
5: iinc 1, -1
8: return
}
从上面我们发现i++和i--其实用的是同一个指令,即iinc,不过操作数不一样罢了;该指令直接修改局部变量区的值,而不需要压栈。至于如何去实现这个指令,不同的人在实现jvm的时候有自己的想法。所以问题一的比较是毫无意义的。你在jvm实现1上运行很快,可能在jvm实现2上面就运行很慢。
对于问题二,如上面普及常识所言,不同的jvm可以有不同的实现,不同的优化。与C语言不同之处在于,java的局部变量区在运行一个方法的时候已经分配好了,不需要遇到一个新变量就去分配。另外一点,因为由jvm来执行,这种循环是jvm内部是存在优化余地的,譬如,对于里层的循环变量,在重新下一次循环时就没用了,一些jvm实现就可以重用这个变量。一些JVM可能是纯粹的解释执行字节码,一些JVM可能在启动的时候就把字节码编译成c++本地代码,一些jvm可能用纯硬件芯片来执行指令集,还有一些jvm在运行了一段时间后找出程序热区,仔细优化并将这部分代码编译成c++本地代码来达到最佳效果。所有你能想到的优化都可以在这里做掉。所以不同的jvm实现对于这样的代码效率可能会有很大的差别。
经过本人实测(循环体是数值计算),IBM J9 1.6中两种都很快,Sun hotspot跟J91.6相比不是一个数量级的。但是IBM J9 1.5的实现则相当的慢,跟hotspot比慢的不是一个数量级。有兴趣的可能分别试试openJDK,IBM J9,Jrockit 这些实现的1.5和1.6版本的效率,另外一些jvm还有server和client运行版本的区别,效率也是大不一样的
总结,隔了一层虚拟机,什么都有可能!