web-dev-qa-db-ja.com

Kotlinでインライン関数を使用する場合

インライン関数がパフォーマンスを改善し、生成されたコードを成長させる可能性があることは知っていますが、いつそれを正しく使用するかはわかりません。

lock(l) { foo() }

コンパイラーは、パラメーターの関数オブジェクトを作成して呼び出しを生成する代わりに、次のコードを発行できます。 ( ソース

l.lock()
try {
  foo()
}
finally {
  l.unlock()
}

しかし、非インライン関数用にkotlinによって作成された関数オブジェクトはないことがわかりました。どうして?

/**non-inline function**/
fun lock(lock: Lock, block: () -> Unit) {
    lock.lock();
    try {
        block();
    } finally {
        lock.unlock();
    }
}
75
holi-java

タイプ() -> Unit(パラメーターなし、戻り値なし)のラムダを受け取る高次関数を作成し、次のように実行するとします。

fun nonInlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

Javaの用語では、これは次のようなものに変換されます(簡略化!):

public void nonInlined(Function block) {
    System.out.println("before");
    block.invoke();
    System.out.println("after");
}

そして、あなたがコトリンからそれを呼ぶとき...

nonInlined {
    println("do something here")
}

内部では、Functionのインスタンスがここで作成され、ラムダ内にコードをラップします(これも簡略化されています)。

nonInlined(new Function() {
    @Override
    public void invoke() {
        System.out.println("do something here");
    }
});

基本的に、この関数を呼び出してラムダを渡すと、常にFunctionオブジェクトのインスタンスが作成されます。


一方、inlineキーワードを使用する場合:

inline fun inlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}

このように呼び出すと:

inlined {
    println("do something here")
}

Functionインスタンスは作成されません。代わりに、インライン関数内のblockの呼び出しを囲むコードが呼び出しサイトにコピーされるため、バイトコードに次のようなコードが表示されます。

System.out.println("before");
System.out.println("do something here");
System.out.println("after");

この場合、新しいインスタンスは作成されません。

212
zsmb13

追加してみましょう: "inlineを使用しない場合"

1)他の関数を引数として受け入れない単純な関数がある場合、それらをインライン化しても意味がありません。 IntelliJはあなたに警告します:

インライン化「...」の予想されるパフォーマンスへの影響はわずかです。インライン化は、機能タイプのパラメーターを持つ機能に最適です。

2)「関数型のパラメーターを持つ」関数を使用している場合でも、インライン化が機能しないことを伝えるコンパイラーが表示される場合があります。この例を考えてみましょう:

inline fun calculateNoInline(param: Int, operation: IntMapper): Int {
    val o = operation //compiler does not like this
    return o(param)
}

このコードはエラーでコンパイルされません:

'...'のインラインパラメーター 'operation'の使用法が正しくありません。パラメーター宣言に「noinline」修飾子を追加します。

その理由は、コンパイラがこのコードをインライン化できないためです。 operationがオブジェクトにラップされていない場合(これを避けるためにinlineによって暗示されます)、どのようにそれを変数に割り当てることができますか?この場合、コンパイラーは引数noinlineを作成することを提案します。単一のinline関数を持つnoinline関数を使用しても意味がありません。そうしないでください。ただし、機能タイプのパラメーターが複数ある場合は、必要に応じてそれらの一部をインライン化することを検討してください。

したがって、ここにいくつかの推奨ルールがあります。

  • 機能タイプparamが直接呼び出されるか、その他のインライン関数に渡される場合、インラインでcanできます
  • ^の場合は、インラインでshouldにします。
  • 関数パラメーターが関数内の変数に割り当てられている場合、インラインでcannot
  • 関数型付きパラメーターの少なくとも1つをインライン化できる場合は、インライン化を検討してくださいshould、残りにはnoinlineを使用します。
  • あなたは巨大な関数をインライン化すべきではありません。生成されたバイトコードについて考えてください。関数が呼び出されるすべての場所にコピーされます。
  • 別の使用例は、reified型パラメーターで、inlineを使用する必要があります。 ここ を読んでください。
14
s1m0nw1

インライン修飾子を使用する最も重要なケースは、util関数のような関数をパラメーター関数で定義する場合です。コレクションまたは文字列の処理(filtermapまたはjoinToStringなど)またはスタンドアロン関数が完璧な例です。

これが、インライン修飾子が主にライブラリ開発者にとって重要な最適化である理由です。彼らは、それがどのように機能し、その改善とコストが何であるかを知っている必要があります。関数型パラメーターを使用して独自のutil関数を定義する場合、プロジェクトでインライン修飾子を使用します。

関数型パラメーター、具体化された型パラメーターがなく、非ローカルリターンが不要な場合、インライン修飾子を使用しないでください。 Android St​​udioまたはIDEA IntelliJで警告が表示されるのはこのためです。

また、コードサイズの問題もあります。大きな関数をインライン化すると、すべての呼び出しサイトにコピーされるため、バイトコードのサイズが劇的に増加する可能性があります。そのような場合、関数をリファクタリングし、コードを通常の関数に抽出できます。

2
0xAliHn

必要になる可能性のある単純なケースの1つは、サスペンドブロックを受け取るutil関数を作成する場合です。このことを考慮。

fun timer(block: () -> Unit) {
    // stuff
    block()
    //stuff
}

fun logic() { }

suspend fun asyncLogic() { }

fun main() {
    timer { logic() }

    // This is an error
    timer { asyncLogic() }
}

この場合、タイマーはサスペンド機能を受け入れません。それを解決するために、あなたもそれを一時停止させたいと思うかもしれません

suspend fun timer(block: suspend () -> Unit) {
    // stuff
    block()
    // stuff
}

ただし、その後はコルーチン/サスペンド関数自体からのみ使用できます。次に、これらのユーティリティの非同期バージョンと非非同期バージョンを作成します。インラインにすると問題はなくなります。

inline fun timer(block: () -> Unit) {
    // stuff
    block()
    // stuff
}

fun main() {
    // timer can be used from anywhere now
    timer { logic() }

    launch {
        timer { asyncLogic() }
    }
}

kotlin playground はエラー状態です。解決するには、タイマーをインラインにします。

0
Anthony Naddeo