近有不少人提出Java循環(huán)優(yōu)化問題,問題分為兩類:
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++) {
。。。
}
}
這個比較主要問題在于循環(huán)次數(shù)多的放在里面還是外面的問題。
我的觀點:首先,這種代碼上自以為是的優(yōu)化是沒有意義的;其次,拿C語言的思維來考慮這個問題表示Java基本常識都不懂。
分析:
在具體闡明我的觀點前有必要做一點Java常識的普及。(注,若沒有特別說明,文中sun的指的是被Oracle收購的那個sun,也用來表示Oracle接手的sun公司的一些產(chǎn)品,如sun的jvm,既指Oracle接手sun后的虛擬機,也指未收購時的sun的虛擬機)
1) jvm。
眾所周知,這是java虛擬機。但是,很多人,包括初學(xué)者甚至一些工作了的人,對jvm的認識僅僅是sun的hotspot虛擬機,就是從sun官網(wǎng)上下載的那個。
而實際上,sun公司制定的是一個規(guī)范,即Java虛擬機規(guī)范,搜索jvmspec即可得。同時sun也提供了該規(guī)范的一個標準實現(xiàn),就是sun的hotspot虛擬機(N年前的版本就不說了)。但很多人不知道的是,除了sun實現(xiàn)了虛擬機外,還有很多公司也根據(jù)自己的需要實現(xiàn)了Java虛擬機,比較常見的有IBM的J9(Websphere中用的),Oracle的JRockit(Weblogic中用的),Apache的Harmony(由于利益等原因,sun和Oracle都沒有給它提供兼容性測試),還有openJDK。這些都是比較流行的。諸如此類,還有很多很多。
2) Java指令集
jvm規(guī)范中,為Java定義了一套指令集。如iadd,iinc等,指令集用單字節(jié)表示,也就是說不超過255個。
關(guān)于規(guī)范中jvm指令集最需要注意的一點是:指令集指描述了指令該做什么事情,對于如何去做,是留給jvm實現(xiàn)者自己去思考的,所以不同的jvm實現(xiàn)對于同一段代碼在效率上可能會有很大的差別。譬如,對于iadd指令,兩個int相加,既可以直接交給硬件去做,也可以拐彎抹角的去做,只要最終結(jié)果符合jvm規(guī)范的描述即可。
3) Java棧
需要知道的是,Java考慮到跨平臺的需要,所有指令的操作都不是基于寄存器的,而是內(nèi)存中的Java棧。在C語言中,有個寄存器用于pc計數(shù)器,而在Java中,pc計數(shù)器是內(nèi)存中的一個字。Java棧由棧幀組成,棧幀分為局部變量區(qū),操作數(shù)棧和幀數(shù)據(jù)區(qū)。jvm指令的操作數(shù)大都源于操作數(shù)棧。這與一些語言從寄存器中取操作數(shù)是不同的。而局部變量區(qū)和操作數(shù)棧的大小在編譯Java文件時就已經(jīng)確定了。
對于問題一,我們有必要看一下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
}
從上面我們發(fā)現(xiàn)i++和i--其實用的是同一個指令,即iinc,不過操作數(shù)不一樣罷了;該指令直接修改局部變量區(qū)的值,而不需要壓棧。至于如何去實現(xiàn)這個指令,不同的人在實現(xiàn)jvm的時候有自己的想法。所以問題一的比較是毫無意義的。你在jvm實現(xiàn)1上運行很快,可能在jvm實現(xiàn)2上面就運行很慢。
對于問題二,如上面普及常識所言,不同的jvm可以有不同的實現(xiàn),不同的優(yōu)化。與C語言不同之處在于,java的局部變量區(qū)在運行一個方法的時候已經(jīng)分配好了,不需要遇到一個新變量就去分配。另外一點,因為由jvm來執(zhí)行,這種循環(huán)是jvm內(nèi)部是存在優(yōu)化余地的,譬如,對于里層的循環(huán)變量,在重新下一次循環(huán)時就沒用了,一些jvm實現(xiàn)就可以重用這個變量。一些JVM可能是純粹的解釋執(zhí)行字節(jié)碼,一些JVM可能在啟動的時候就把字節(jié)碼編譯成c++本地代碼,一些jvm可能用純硬件芯片來執(zhí)行指令集,還有一些jvm在運行了一段時間后找出程序熱區(qū),仔細優(yōu)化并將這部分代碼編譯成c++本地代碼來達到最佳效果。所有你能想到的優(yōu)化都可以在這里做掉。所以不同的jvm實現(xiàn)對于這樣的代碼效率可能會有很大的差別。
經(jīng)過本人實測(循環(huán)體是數(shù)值計算),IBM J9 1.6中兩種都很快,Sun hotspot跟J91.6相比不是一個數(shù)量級的。但是IBM J9 1.5的實現(xiàn)則相當(dāng)?shù)穆?,?/span>hotspot比慢的不是一個數(shù)量級。有興趣的可能分別試試openJDK,IBM J9,Jrockit 這些實現(xiàn)的1.5和1.6版本的效率,另外一些jvm還有server和client運行版本的區(qū)別,效率也是大不一樣的
總結(jié),隔了一層虛擬機,什么都有可能!