Yabu.log

ITなどの雑記

Kotlinでラムダ式のインライン展開を逆コンパイルで検証

Java読書会で読んだKotlin in Actionの復習です。 最初はQiitaに投稿するつもりで書いたのですが、 ただ逆コンパイルしたソースを貼り付けてるだけなのでブログに書きます。

インライン関数とそのメリットについて

Kotlinではラムダ式は無名クラスにコンパイルされる

  • ラムダは無名クラスにコンパイルされます。
  • 呼び出しのたびに新しいオブジェクトが生成されます
  • 実行時のオーバーヘッドとなる

ただし、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のパフォーマンスに関する知識がないため、特にこの部分で説明できることがない
    • インライン展開の方がパフォーマンス的には有利らしい。

Kotlinイン・アクション

Kotlinイン・アクション