JavaではJITコンパイラがインライン展開を行っているということは聞いたことがあるのですが、それによってどれだけの恩恵を受けているのか、ちょっと実験してみたくなりました。
そもそもインライン展開とは
詳細はWikipediaを参照してください。
要はメソッドの呼び出しにはオーバーヘッドがあるため、メソッド内の処理をメソッドの呼び出し側に埋め込んでしまえば、メソッド呼び出しのコストを抑えることができるということですね。JITコンパイラが必要に応じてこれを行っています。
実験
今回試してみるコードは以下の通りです。StringBuilderに文字列をappendするだけのメソッドを用意して、繰り返し呼び出してみました。
同じメソッドを何度も呼び出していますので、インライン展開される/されないによってパフォーマンスに差が出るのではないかと仮説を立ててみました。
public class InlineSample {
public static void main(String[] args) {
long start = System.currentTimeMillis();
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 10_000_000; i++) {
appendHoge(builder);
}
System.out.println(builder.length());
long end = System.currentTimeMillis();
System.out.println("elapsed " + (end - start) + " ms");
}
private static void appendHoge(StringBuilder builder) {
builder.append("hoge");
}
}
このコードをインライン化を有効/無効の両方のパターンで実行して性能を検証しました。
インライン化はデフォルトで有効になっているため、以下のVMパラメータを追加することによって無効化しました。
-XX:-Inline
なお、Windows10、Java15-eaの環境で試しています。
結果
5回試行して平均をとったところ、以下のような結果になりました。
インライン展開ON: 平均 125ms
インライン展開OFF: 平均 342ms
結構な差ですね。
ただ、JITコンパイルはプログラム実行中に行われますので、プログラムの実行開始時からインライン展開されていたわけではありません(-XX:+PrintInlining オプションによりインライン展開のログを確認しました)。この点ではあまり公平な比較にはなっていません。
ベンチマークツールのJMHを使ったほうがよかったかなと思いますが、ともあれ、これだけの差がつくとは驚きです。
なお、インライン展開の対象となるメソッドのサイズは
-XX:MaxInlineSize=<size>
により、指定することができます。デフォルトは35バイトです。
インライン展開を意識したコードを書くことによって、よりJITコンパイラの恩恵を受けることができるかもしれませんね。
参考