Kotlinでラムダ式のインライン展開を逆コンパイルで検証
Java読書会で読んだKotlin in Actionの復習です。 最初はQiitaに投稿するつもりで書いたのですが、 ただ逆コンパイルしたソースを貼り付けてるだけなのでブログに書きます。
インライン関数とそのメリットについて
- ラムダは無名クラスにコンパイルされます。
- 呼び出しのたびに新しいオブジェクトが生成されます
- 実行時のオーバーヘッドとなる
ただし、inline修飾子がある場合はラムダ式がインライン展開され匿名クラスが作られない。 実行ファイルが大きくなる、などのデメリットもあるようだが、呼び出しコストが下がるというメリットがある。
- Kolinの標準ライブラリの関数はほとんどinlineになっている
コンパイル対象のKotlinのソース
fun main(args: Array<String>) { doTenTimes { println("hell world") } } inline fun doTenTimes(function:()->Unit){ for(i in 1 .. 10){ function() } }
このdoTenTimes()のinline修飾子の有無を変えて調査しました。
コンパイル結果
inline 修飾子がない場合
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) annotate // Source File Name: hello.kt import java.io.PrintStream; import kotlin.Unit; import kotlin.jvm.functions.Function0; import kotlin.jvm.internal.Intrinsics; import kotlin.jvm.internal.Lambda; public final class HelloKt { public static final void main(String args[]) { Intrinsics.checkParameterIsNotNull(args, "args"); // 0 0:aload_0 // 1 1:ldc1 #9 <String "args"> // 2 3:invokestatic #15 <Method void Intrinsics.checkParameterIsNotNull(Object, String)> static final class main._cls1 extends Lambda implements Function0 { public volatile Object invoke() { invoke(); // 0 0:aload_0 // 1 1:invokevirtual #12 <Method void invoke()> return Unit.INSTANCE; // 2 4:getstatic #18 <Field Unit Unit.INSTANCE> // 3 7:areturn } public final void invoke() { String s = "hell world"; // 0 0:ldc1 #20 <String "hell world"> // 1 2:astore_1 System.out.println(s); // 2 3:getstatic #26 <Field PrintStream System.out> // 3 6:aload_1 // 4 7:invokevirtual #32 <Method void PrintStream.println(Object)> // 5 10:return } public static final main._cls1 INSTANCE = new main._cls1(); static { // 0 0:new #2 <Class HelloKt$main$1> // 1 3:dup // 2 4:invokespecial #60 <Method void HelloKt$main$1()> // 3 7:putstatic #62 <Field HelloKt$main$1 INSTANCE> //* 4 10:return } } doTenTimes((Function0)main._cls1.INSTANCE); // 3 6:getstatic #21 <Field HelloKt$main$1 HelloKt$main$1.INSTANCE> // 4 9:checkcast #23 <Class Function0> // 5 12:invokestatic #27 <Method void doTenTimes(Function0)> // 6 15:return } public static final void doTenTimes(Function0 function) { Intrinsics.checkParameterIsNotNull(function, "function"); // 0 0:aload_0 // 1 1:ldc1 #30 <String "function"> // 2 3:invokestatic #15 <Method void Intrinsics.checkParameterIsNotNull(Object, String)> int i = 1; // 3 6:iconst_1 // 4 7:istore_1 for(byte byte0 = 10; i <= byte0; i++) //* 5 8:bipush 10 //* 6 10:istore_2 //* 7 11:iload_1 //* 8 12:iload_2 //* 9 13:icmpgt 29 function.invoke(); // 10 16:aload_0 // 11 17:invokeinterface #34 <Method Object Function0.invoke()> // 12 22:pop // 13 23:iinc 1 1 //* 14 26:goto 11 // 15 29:return } }
inline 展開ありの場合
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov. // Jad home page: http://www.kpdus.com/jad.html // Decompiler options: packimports(3) annotate // Source File Name: hello.kt import java.io.PrintStream; import kotlin.jvm.functions.Function0; import kotlin.jvm.internal.Intrinsics; public final class HelloKt { public static final void main(String args[]) { Intrinsics.checkParameterIsNotNull(args, "args"); // 0 0:aload_0 // 1 1:ldc1 #9 <String "args"> // 2 3:invokestatic #15 <Method void Intrinsics.checkParameterIsNotNull(Object, String)> //* 3 6:nop int i$iv = 1; // 4 7:iconst_1 // 5 8:istore_1 for(byte byte0 = 10; i$iv <= byte0; i$iv++) //* 6 9:bipush 10 //* 7 11:istore_2 //* 8 12:iload_1 //* 9 13:iload_2 //* 10 14:icmpgt 35 //* 11 17:nop { String s = "hell world"; // 12 18:ldc1 #17 <String "hell world"> // 13 20:astore_3 System.out.println(s); // 14 21:getstatic #23 <Field PrintStream System.out> // 15 24:aload_3 // 16 25:invokevirtual #29 <Method void PrintStream.println(Object)> } // 17 28:nop // 18 29:iinc 1 1 //* 19 32:goto 12 // 20 35:nop // 21 36:return } public static final void doTenTimes(Function0 function) { Intrinsics.checkParameterIsNotNull(function, "function"); // 0 0:aload_0 // 1 1:ldc1 #38 <String "function"> // 2 3:invokestatic #15 <Method void Intrinsics.checkParameterIsNotNull(Object, String)> int i = 1; // 3 6:iconst_1 // 4 7:istore_2 for(byte byte0 = 10; i <= byte0; i++) //* 5 8:bipush 10 //* 6 10:istore_3 //* 7 11:iload_2 //* 8 12:iload_3 //* 9 13:icmpgt 29 function.invoke(); // 10 16:aload_0 // 11 17:invokeinterface #44 <Method Object Function0.invoke()> // 12 22:pop // 13 23:iinc 2 1 //* 14 26:goto 11 // 15 29:return } }
- inline修飾子をつけることでコンパイル後のバイトコードがかなり違うことがわかる。
- 匿名クラスを引数にした関数呼び出しのコストなどはJavaのパフォーマンスに関する知識がないため、特にこの部分で説明できることがない
- インライン展開の方がパフォーマンス的には有利らしい。
- 作者: Dmitry Jemerov,Svetlana Isakova,長澤太郎,藤原聖,山本純平,yy_yank
- 出版社/メーカー: マイナビ出版
- 発売日: 2017/10/31
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る