Javaパフォーマンス計測 JITとの闘い

前回の文字列操作編では適当に文字列操作のパフォーマンスを測定しようとしたらGC様とJITコンパイラ様に阻まれた、という話だった。モヒカン族*1が「てめえの計測はなっちゃいねー!ひゃっはー!」と殴りかかったらケンシロウみたいなのが出てきて「あべしっ」となった、ぐらいのつまらない話だったが、反省してこれらと向かい合ってみたい。

JITコンパイラについての情報

JITコンパイラ(Just In Time compiler)とはインタープリタ方式のプログラム言語のランタイムが実行時に必要に応じて部分的にネイティブコード(CPUが直接実行できるマシン語)に変換することで高速化するというコンパイラである。もともとはもっと狭義のニュアンスだったが、今ではJITコンパイルとHotSpot動的コンパイルを併せて広義にJITコンパイル、それを実施する実態をJITコンパイラと呼んでいる感じだ。*2

ただ、やみくもにコンパイルすればいいわけではなくて、たとえば繰り返しのないプログラムがあった場合、コンパイルしてから実行するよりもそのままインタプリタ方式で実行するほうが速い場合が多い。コンパイルの為にはコードをひと通りなめてコンパイル処理をしなければならない。同じひと通りなめるならそのままインタプリタとして実行してしまえばいい。

そのため、ホットスポットと呼ばれる実行回数の多い個所を探して集中的にコンパイルするという手法がとられる。

アドバイスと資料

前回の計測の問題点を id:skrb 氏に指摘してもらった。

この場合、ループがインラインで展開されることも考慮しなくてはならないです。できればインライン展開されないようなループにした方が連結の正確な時間を計れるはず。
synchronizedによるロックはライトウェイトロックでロックにかかる時間は短いので、この程度で収まっていると思われます。ライトウェイトロックなので、ロックをはずすのも簡単にできるようになっているようです。
ちなみに、HotSpotのネイティブコンパイラはデフォルトで10回コールされるとコンパイルするはずです(ClientVMの場合)。

http://d.hatena.ne.jp/Nagise/comment?date=20110222§ion=1298394310#c

最適化で考慮するべき事項は多そうだ。ロックが外された可能性はありそう、インライン展開を考慮した方がいい、10回でコンパイルされる、JITコンパイルはメソッド単位。

コンパイルと最適化の単位

コンパイルはメソッド単位だが、最適化はメソッド単位より細かく行われることがある、メソッド内で閉じている文字列連結ではエスケープアナリシスが使われているはず。エスケープ解析については「メモリーを意識してみよう」第4回 進化するメモリー管理 | 日経 xTECH(クロステック)が詳しい。

ベンチマークの難しさ

Java 6のスレッド最適化は実際に動作しているのか?その2では、まさにパフォーマンス測定の難しさについて書いてある。「StringBuffer対StringBuilderのベンチマーク」の項では

「Stringの代わりにStringBufferを使用すると、どれほどコストの節約になるのか?」と言う古い質問でした。(中略)StringBuilderとStringBufferの間の違いは、単に同期の有無だけです。この二つをベンチマークで測定し、パフォーマンスに違いが生じるなら、それは同期のコストが表面化しているのだ、と考えられます。
(中略)
私の同僚たちによって行われたテストでは結果が異なり、結果の正確性が怪しまれる結果となってしまいました。

Java 6のスレッド最適化は実際に動作しているのか?

与えられた質問に答える事を目的としてベンチマーク、特にマイクロベンチマークを行うのは、とりわけ困難なものになり得ます。しばしばベンチマークは、あなたが測定しようとするものとは完全に異なるもので終わってしまう事があります。問題になっている効果を測定しようとしているときでも、他の影響によって測定結果が狂わされてしまいます。

Java 6のスレッド最適化は実際に動作しているのか? - パートII

まさに、同じようなことを追体験した、というわけだ。

動的コンパイル

Javaの理論と実践: 動的コンパイルとパフォーマンス測定は2004年の記事。

2004年というとちょうどJava5のリリースされた時期だ。

動的コンパイルの簡単な歴史が紹介されていて、Just-in-timeコンパイル、HotSpot動的コンパイル、連続再コンパイル、On-stack replacementといった技術について紹介されている。そしてベンチマークの怪しい結果の例を挙げてくれている。

  • デッドコード削除(Dead-code elimination)
  • ウォームアップ
  • ガーベジ・コレクションを忘れずに
  • ダイナミック・デオプティマイゼーション(dynamic deoptimization)

こうした事例を踏まえて、計測コードを修正しなくてはいけない。

結論

パフォーマンス計測は難しい。

疲れたのでリベンジは次回へ持ち越し。