Inlining or not?

前回のエントリで、インライン化についてちょっと触れました。

ちなみにCharacter#charCountは補助文字の場合に2が、それ以外は1が返るだけなので、インライン展開すればもしかすると違ってくるかも。でもJITコンパイラがそこまでやってそうな気もします。一応PrintCompilationオプションを使ってgetUtf8がJITコンパイルされていることは確認しました。

そこで、インライン化されているかどうか確認してみました。

OpenJDK debug build and JVM Option

インライン化されているか確認する為には、「-XX:+PrintInlining」を付けて実行します。

但し、このオプションはdebug build版のJVMでのみ有効になるということなので、今回はOpenJDK7のコードを取得してdebug buildして使っています。OpenJDKのdebug buildについて以下のエントリを参考にしました。

Output

早速、BSONEncoder#_putのUTF-8変換処理を抜き出したコードを「-XX:+PrintInlining」を付けて実行しました。

また、どのメソッドの中でインライン化されたか確認する為に「-XX:+PrintCompilation」も付けています。

 1   1065    9   !     net.wrap_trap.example.encode.EncodeUTF8Test::getUtf8 (279 bytes)
 2                        @ 7   java.lang.String::length (5 bytes)
 3                        @ 12   java.io.ByteArrayOutputStream::<init> (43 bytes)   callee is too large
 4                        @ 17   java.lang.String::length (5 bytes)
 5              s         @ 240   java.io.ByteArrayOutputStream::toByteArray (12 bytes)
 6                          @ 8   java.util.Arrays::copyOf (19 bytes)
 7                            @ 11   java.lang.Math::min (11 bytes)
 8                            @ 14   java.lang.System::arraycopy (0 bytes)
 9                        @ 250   java.io.ByteArrayOutputStream::close (1 bytes)
10                        @ 32   java.lang.Character::codePointAt (51 bytes)   callee is too large
11              s         @ 49   java.io.ByteArrayOutputStream::write (32 bytes)
12                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
13                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
14              s         @ 77   java.io.ByteArrayOutputStream::write (32 bytes)
15                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
16                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
17              s         @ 91   java.io.ByteArrayOutputStream::write (32 bytes)
18                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
19                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
20              s         @ 118   java.io.ByteArrayOutputStream::write (32 bytes)
21                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
22                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
23              s         @ 135   java.io.ByteArrayOutputStream::write (32 bytes)
24                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
25                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
26              s         @ 149   java.io.ByteArrayOutputStream::write (32 bytes)
27                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
28                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
29              s         @ 169   java.io.ByteArrayOutputStream::write (32 bytes)
30                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
31                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
32              s         @ 186   java.io.ByteArrayOutputStream::write (32 bytes)
33                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
34                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
35              s         @ 203   java.io.ByteArrayOutputStream::write (32 bytes)
36                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
37                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
38              s         @ 217   java.io.ByteArrayOutputStream::write (32 bytes)
39                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
40                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)   callee is too large
41                        @ 227   java.lang.Character::charCount (12 bytes)
42                        @ 268   java.io.ByteArrayOutputStream::close (1 bytes)

出力がかなり多いので、getUtf8メソッドの箇所のみを抜き出しました。

1行目は「-XX:+PrintCompilation」の出力です。その他はすべて「-XX:+PrintInlining」による出力内容となります。

出力フォーマットの説明を見つけることができなかったので認識違いがあるかもしれませんが、getUtf8メソッドの中でインライン化されたメソッドが「@」に続いて出力されます。そのメソッドからさらに別のメソッドを呼び出してインライン化された場合は、calleeを下に記述しインデントして出力されます。

java.io.ByteArrayOutputStream::growの行の最後に「callee is too large」と表示されている場合は、コードのサイズが大きすぎてインライン化できないこと示しているようです。

最後から2行目に、java.lang.Character::charCountが出力されていることから、前回記述したとおり、このメソッドの呼び出しはJITコンパイルによってgetUtf8メソッド内にインライン化されていました。

MaxInlineSize

インライン化するかどうかを決定するコードサイズは、「-XX:MaxInlineSize」で指定します。デフォルトは35です。

「-XX:MaxInlineSize=100」として再度実行したところ、前回よりインライン化が行われるようになり、出力結果が大きくなった為、getUtf8の一部だけを抜き出しています。

 1              s         @ 186   java.io.ByteArrayOutputStream::write (32 bytes)
 2                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
 3                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)
 4                              @ 30   java.lang.OutOfMemoryError::<init> (5 bytes)   don't inline Throwable constructors
 5                              @ 43   java.util.Arrays::copyOf (19 bytes)
 6                                @ 11   java.lang.Math::min (11 bytes)
 7                                @ 14   java.lang.System::arraycopy (0 bytes)
 8              s         @ 203   java.io.ByteArrayOutputStream::write (32 bytes)
 9                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
10                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)
11                              @ 30   java.lang.OutOfMemoryError::<init> (5 bytes)   don't inline Throwable constructors
12                              @ 43   java.util.Arrays::copyOf (19 bytes)
13                                @ 11   java.lang.Math::min (11 bytes)
14                                @ 14   java.lang.System::arraycopy (0 bytes)
15              s         @ 217   java.io.ByteArrayOutputStream::write (32 bytes)
16                          @ 7   java.io.ByteArrayOutputStream::ensureCapacity (16 bytes)
17                            @ 12   java.io.ByteArrayOutputStream::grow (50 bytes)
18                              @ 30   java.lang.OutOfMemoryError::<init> (5 bytes)   don't inline Throwable constructors
19                              @ 43   java.util.Arrays::copyOf (19 bytes)
20                                @ 11   java.lang.Math::min (11 bytes)
21                                @ 14   java.lang.System::arraycopy (0 bytes)
22                        @ 227   java.lang.Character::charCount (12 bytes)
23                        @ 268   java.io.ByteArrayOutputStream::close (1 bytes)

-XX:MaxInlineSize=100を指定したことにより、java.io.ByteArrayOutputStream::growがインライン化され、行末に「callee is too large」と表示されなくなりました。

さらにjava.io.ByteArrayOutputStream:grow以降のメソッド呼び出しもインライン化されるようになったことが分かります。何故かOutOfMemoryのコンストラクタが表示されていますが…。この行末に出力されているように、Throwableのコンストラクタはインライン化されないようです。

ちなみに、MaxInlineSize=100を指定したところ、処理時間が4倍近く延びました。過剰なインライン化は速度低下を招くということなのかな。

Conclusion