Kotlinのinlineとcrossinlineの挙動をバイトコードで追う

# ただのメソッドの呼び出し
Kotlinでこのようなコードを書いたとき

```kotlin
fun main() {
    doSomeThing()
}

fun doSomeThing() {
    println("This is doSomeThing")
}
```

バイトコードはこんな感じになってます。 `main()`の中で `doSomeThing()`を呼び出すということを行っています。

```java
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の性質です。他のメソッドを呼び出すというのは少なからずコストがかかることで、それをなくす形で同じように実行してくれるというものです。

```kotlin
fun main() {
    doSomeThing()
}

inline fun doSomeThing() {
    println("This is doSomeThing")
}
```

バイトコード

```java
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だよと言われます。

![Screen_Shot_2021-03-25_at_1.25.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185927/7afca180-5411-10bb-c5b4-34d5adeb7e47.png)

# bestなinline

`doSomeThing()`にラムダを引数に取るように変更しました。さきほどのワーニングも消えました。

```kotlin
fun main() {
    doSomeThing {
        println("This is block")
    }
}

inline fun doSomeThing(block: () -> Unit) {
    println("This is doSomeThing")
    block()
}
```

このときのバイトコードがどうなってるか同じように確認してみましょう。

```java

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`をつけた場合のバイトコードを見てみましょう。

```kotlin
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()`が呼ばれて実行されています。

```java
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` メソッドは中身の処理はそこまで多くないけど、色々なとこで呼ばれるケースがあるとき効果を発揮します。