インライン関数がパフォーマンスを改善し、生成されたコードを成長させる可能性があることは知っていますが、いつそれを正しく使用するかはわかりません。
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();
}
}
タイプ() -> 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");
この場合、新しいインスタンスは作成されません。
追加してみましょう: "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
関数を使用しても意味がありません。そうしないでください。ただし、機能タイプのパラメーターが複数ある場合は、必要に応じてそれらの一部をインライン化することを検討してください。
したがって、ここにいくつかの推奨ルールがあります。
noinline
を使用します。reified
型パラメーターで、inline
を使用する必要があります。 ここ を読んでください。インライン修飾子を使用する最も重要なケースは、util関数のような関数をパラメーター関数で定義する場合です。コレクションまたは文字列の処理(filter
、ma
pまたはjoinToString
など)またはスタンドアロン関数が完璧な例です。
これが、インライン修飾子が主にライブラリ開発者にとって重要な最適化である理由です。彼らは、それがどのように機能し、その改善とコストが何であるかを知っている必要があります。関数型パラメーターを使用して独自のutil関数を定義する場合、プロジェクトでインライン修飾子を使用します。
関数型パラメーター、具体化された型パラメーターがなく、非ローカルリターンが不要な場合、インライン修飾子を使用しないでください。 Android StudioまたはIDEA IntelliJで警告が表示されるのはこのためです。
また、コードサイズの問題もあります。大きな関数をインライン化すると、すべての呼び出しサイトにコピーされるため、バイトコードのサイズが劇的に増加する可能性があります。そのような場合、関数をリファクタリングし、コードを通常の関数に抽出できます。
必要になる可能性のある単純なケースの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 はエラー状態です。解決するには、タイマーをインラインにします。