JVMのパフォーマンスチューニング

最近仕事でパフォマンスチューニングをやってるんだけど。意外とJVMのパラメータ変えるだけで性能があがったりするもんだね。

そこで、JVMのオプションについていろいろ調べてみようかなと思って、下記URLの記事を参考にちょっと試してみた。
環境はUbuntu10.04 Javaのバージョンは以下の通り

$ java -version
java version "1.6.0_20"
Java(TM) SE Runtime Environment (build 1.6.0_20-b02)
Java HotSpot(TM) Server VM (build 16.3-b01, mixed mode)

(お。Server VMってなってる)

HotSpot VMの特性を知る (1/2):Javaパフォーマンスチューニング(6) - @IT
ほとんど上の記事と同じコードで、1千万回ループしてその処理時間を取るというもの。

public class LoopTest {
        public static void main(String[] args) {
                long start = System.currentTimeMillis();
                long value = 0;
                for (int i=0; i<10000000; i++) {
                        value += i;
                }
                System.out.println("exec time: " + (System.currentTimeMillis() - start));
        }
}

これをコンパイルしてそのまま実行してみる。

$ javac LoopTest.java
$ java LoopTest 
exec time: 7

って、速いし。上の記事によると1億回実行してるので、1億回にしてコンパイル&実行してみる。しかし結果は変わらない。なんか、他の最適化が動いてる感じ??

$ java -Xint LoopTest 
exec time: 236

なるほど増えたね。てことは、HotSpotのコンパイルが走ってる感じだね。上の記事のJavaとバージョンが違うからだろうか。


次に、-XX:CompileThresholdというオプションがあるので、これをつけてみる。これは、メソッドが指定回数呼び出されたりするとコードがコンパイルされるものらしい。これを、上記コードの1千万回より多くしておくと、Xintを指定した時と同じように処理が遅くなるはず(つまり、ループが終わるまでHotSpotによる最適化が行われないから)

$ java -XX:CompileThreshold=10000000 LoopTest 
exec time: 269

お。なんか予想通り。おそくなった。てことは、ループだとしてもこのオプションは効いてるってことかね?
VMのドキュメントはどこが最新なのか良くわからないけど)下記の-XX:CompileThresholdオプションの説明によると、

コンパイル前のメソッド呼び出し/分岐の数 [10,000 ― Sparc サーバ、1,000 ― Sparc クライアント、1,500 ― x86 クライアント]

http://docs.sun.com/app/docs/doc/816-3973/6ma7ftaqh?l=ja&a=view

ということらしい、「コンパイル前のメソッド呼び出し/分岐の数」とあるけどループもこれに含まれるってことかな。

また、デフォルトでは(私の環境の場合)10,000が指定されているみたい(java -version でServer VMと表示されていたし)。なので、これで実行してみる。

$ java -XX:CompileThreshold=10000 LoopTest 
exec time: 6

おお。それっぽい。つまり、私の環境のデフォルトだと-XX:CompileThresholdオプションに10,000が指定されていたから、予想より速かったって訳だね。納得。


てことで、この値を1千万回から徐々に少なくしてみた。

$ java -XX:CompileThreshold=10000000 LoopTest 
exec time: 259

$ java -XX:CompileThreshold=1000000 LoopTest 
exec time: 44

$ java -XX:CompileThreshold=100000 LoopTest 
exec time: 10

$ java -XX:CompileThreshold=10000 LoopTest 
exec time: 7

おお。それっぽい結果に。コンパイルされるタイミングが早くなるほど、最適化されたコードがたくさん動いて処理時間も短くなるってことね。じゃあ、もっとこの値を小さくするとどうなるか。

$ java -XX:CompileThreshold=1000 LoopTest 
exec time: 19

$ java -XX:CompileThreshold=100 LoopTest 
exec time: 134

$ java -XX:CompileThreshold=10 LoopTest 
exec time: 344

$ java -XX:CompileThreshold=1 LoopTest 
exec time: 422

おお。どんどん遅くなった!!なぜ。

む。こっちのドキュメントには「メソッドの呼び出し / 分岐の数がこの値に達したら (再) コンパイルを行う」とある。
日本オラクル | Integrated Cloud Applications and Platform Services

”再”コンパイルなのか??もしそうだとしたら、、、-XX:CompileThresholdで指定した周期でコンパイルが走るとしたら、、、、小さくしすぎるのも問題ってことだね。多分、10としたときに10回ループするたびにコンパイルが実行されそっちの方のコストが高くなるんだと思われる。

ということで、対象のアプリケーションがどのくらいの頻度で呼び出されるのか、とか、どのくらいメソッド呼び出しがあるのかとか知っておくとチューニングの助けになりそうだね。(まぁ、あとはコンパイルのコストがどれだけか知りたいところだけど、これもオプションあった気がするが、また次の機会にでも)


あとは、ひたすら最適な値を弾き出す地道な作業が必要なのね。

上記のコードを私の環境で動かした時の-XX:CompileThresholdの値の最適なところは「1,800」といったところでした。この値にしておくと1msで処理が終わることが多かった!やった。

$ java -XX:CompileThreshold=1800 LoopTest 
exec time: 1