Andreasが言及した問題とJITの予測不可能性は真実ですが、それでももう1つの問題はクラスローダーです。
への最初の呼び出しfOne
は、後者の呼び出しとは根本的に異なりますSystem.out.println
。これは、への最初の呼び出しを行うためです。つまり、クラスローダーがディスクまたはファイルシステムのキャッシュ(通常はキャッシュされている)から、印刷に必要なすべてのクラスを取得します。文章。-verbose:class
この小さなプログラム中に実際にロードされるクラスの数を確認するには、JVMにパラメーターを指定します。
単体テストを実行するときに同様の動作に気づきました-大きなフレームワークを呼び出す最初のテストは、テストコードが同じであっても、はるかに長い時間がかかります(C2Q6600で約250msのGuiceの場合)。何百ものクラスがクラスローダーによってロードされます。
サンプルプログラムは非常に短いため、オーバーヘッドはおそらく非常に初期のJIT最適化とクラスローディングアクティビティに起因します。ガベージコレクターは、プログラムが終了する前に起動することすらありません。
アップデート:
今、私は本当に時間がかかっているものを見つけるための信頼できる方法を見つけました。それはクラスのロードと密接に関連していますが、まだ誰もそれを発見していませんでした-それはネイティブメソッドの動的リンクでした!
次のようにコードを変更して、テストの開始時と終了時にログが表示されるようにしました(これらの空のマーカークラスがいつロードされるかを確認します)。
TestIO t = new TestIO();
new TestMarker1();
t.fOne();
t.fTwo();
t.fOne();
t.fTwo();
new TestMarker2();
プログラムを実行するためのコマンド。実際に何が起こっているかを示す適切なJVMパラメーターを使用します。
java -verbose:class -verbose:jni -verbose:gc -XX:+PrintCompilation TestIO
そして出力:
* snip 493 lines *
[Loaded java.security.Principal from shared objects file]
[Loaded java.security.cert.Certificate from shared objects file]
[Dynamic-linking native method java.lang.ClassLoader.defineClass1 ... JNI]
[Loaded TestIO from file:/D:/DEVEL/Test/classes/]
3 java.lang.String::indexOf (166 bytes)
[Loaded TestMarker1 from file:/D:/DEVEL/Test/classes/]
[Dynamic-linking native method java.io.FileOutputStream.writeBytes ... JNI]
100
Time taken by 'fOne' ... 155354
100
Time taken by 'fTwo' ... 23684
100
Time taken by 'fOne' ... 22672
100
Time taken by 'fTwo' ... 23954
[Loaded TestMarker2 from file:/D:/DEVEL/Test/classes/]
[Loaded java.util.AbstractList$Itr from shared objects file]
[Loaded java.util.IdentityHashMap$KeySet from shared objects file]
* snip 7 lines *
そして、その時差の理由はこれです:[Dynamic-linking native method java.io.FileOutputStream.writeBytes ... JNI]
また、JITコンパイラがこのベンチマークに影響を与えていないこともわかります。コンパイルされるメソッドは3つだけで(java.lang.String::indexOf
上記のスニペットなど)、fOne
メソッドが呼び出される前にすべて実行されます。