Kotlinのinlineとcrossinlineの挙動をバイトコードで追う
この記事は、Kotlinのinlineとcrossinlineの挙動をバイトコードで追う - Qiita のコピーです。
ただのメソッドの呼び出し
Kotlinでこのようなコードを書いたとき
fun main() { doSomeThing() } fun doSomeThing() { println("This is doSomeThing") }
バイトコードはこんな感じになってます。 main()
の中で doSomeThing()
を呼び出すということを行っています。
public final class InlineCrossinlineKt { public static final void main() { doSomeThing(); } // $FF: synthetic method public static void main(String[] var0) { main(); } public static final void doSomeThing() { String var0 = "This is doSomeThing"; boolean var1 = false; System.out.println(var0); } }
inlineがやること
さっきまであった main()
の中の doSomeThing()
の呼び出しがなくなって、 doSomeThing()
の中身が main()
の中に展開された形になります。これがinlineの性質です。他のメソッドを呼び出すというのは少なからずコストがかかることで、それをなくす形で同じように実行してくれるというものです。
fun main() { doSomeThing() } inline fun doSomeThing() { println("This is doSomeThing") }
public final class InlineCrossinlineKt { public static final void main() { int $i$f$doSomeThing = false; String var1 = "This is doSomeThing"; boolean var2 = false; System.out.println(var1); } // $FF: synthetic method public static void main(String[] var0) { main(); } public static final void doSomeThing() { int $i$f$doSomeThing = 0; String var1 = "This is doSomeThing"; boolean var2 = false; System.out.println(var1); } }
ちなみにこのコードをIDEで書いた場合、このinlineはあまり意味がないよとワーニングが起こり、inlineをつけるときは、引数に関数型を持つメソッドにつけるとbestだよと言われます。
bestなinline
doSomeThing()
にラムダを引数に取るように変更しました。さきほどのワーニングも消えました。
fun main() { doSomeThing { println("This is block") } } inline fun doSomeThing(block: () -> Unit) { println("This is doSomeThing") block() }
このときのバイトコードがどうなってるか同じように確認してみましょう。
public final class InlineCrossinlineKt { public static final void main() { int $i$f$doSomeThing = false; String var1 = "This is doSomeThing"; boolean var2 = false; System.out.println(var1); int var3 = false; String var4 = "This is block"; boolean var5 = false; System.out.println(var4); } // $FF: synthetic method public static void main(String[] var0) { main(); } public static final void doSomeThing(@NotNull Function0 block) { int $i$f$doSomeThing = 0; Intrinsics.checkNotNullParameter(block, "block"); String var2 = "This is doSomeThing"; boolean var3 = false; System.out.println(var2); block.invoke(); } }
先ほどと同じように、 doSomeThing()
の中の This is doSomeThing
はinlineの影響で main()
の中で直接 println()
されたかのように振る舞います。
そして、今回 doSomeThing()
の引数に追加したラムダである block()
は doSomeThing()
で実行されて This is block
が出力されるわけではなく、 main()
の中で block()
の処理が直接展開されて実行されます。
実行結果は一緒ですが、中ではこのような処理の違いが起こります。
そして、ラムダの前に crossinline
をつけてdoSomeThing(crossinline block: () → Unit)
としたとき、バイトコードは同じになります。
なので、 inline fun
の引数がラムダを持つとき、そのラムダは crossinline
の影響によって、そのラムダの中身の処理は呼び出し元の main()
の中に展開されたような振る舞いをします。
ちなみに、 block()
に noinline
をつけた場合のバイトコードを見てみましょう。
fun main() { doSomeThing { println("This is block") } } inline fun doSomeThing(noinline block: () -> Unit) { println("This is doSomeThing") block() }
この場合、 inline
の影響で doSomeThing()
の処理である This is doSomeThing
の出力は main()
の中で行われますが、 block()
は doSomeThing()
の中で block.invoke()
が呼ばれて実行されています。
public final class InlineCrossinlineKt { public static final void main() { Function0 block$iv = (Function0)null.INSTANCE; int $i$f$doSomeThing = false; String var2 = "This is doSomeThing"; boolean var3 = false; System.out.println(var2); block$iv.invoke(); } // $FF: synthetic method public static void main(String[] var0) { main(); } public static final void doSomeThing(@NotNull Function0 block) { int $i$f$doSomeThing = 0; Intrinsics.checkNotNullParameter(block, "block"); String var2 = "This is doSomeThing"; boolean var3 = false; System.out.println(var2); block.invoke(); } }
inlineのメリット・デメリット
inline
をつけたメソッドを定義することで、そのメソッドの中身の処理がそのままコピーされたように呼び出し元に展開されて実行される挙動になります。これにより、inline
メソッドの中身の処理が呼び出し元にそのまま書かれているように実行され、メソッドを呼ぶコストを削減できます。 inline
メソッドの中身の処理によると思いますが、少しパフォーマンスの良いコードを書くことができるようになるというわけです。
じゃあ、全部 inline
つければすっごいパフォーマンスいいコード書けるようになるんじゃないか?と思うかもしれません。
しかし、Decompileしたバイトコードを見てみると、 inline
をつけた場合その中身の処理のコピーが呼び出し元に展開されて、 inline
メソッドを呼び出せば呼び出すほどこのコピーが増えてバイトコードの容量も大きくなっていきます。なので、 inline
をつければつけるほど apkファイルなどのファイルサイズが大きくなってしまうことが挙げられます。これが inline
をつけるときのデメリットというか、適材適所という訳です。
なので、 inline
メソッドは中身の処理はそこまで多くないけど、色々なとこで呼ばれるケースがあるとき効果を発揮します。