近有不少人提出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運行版本的區別,效率也是大不一樣的
總結,隔了一層虛擬機,什么都有可能!