問題の歴史:
JVMにおける関数型プログラミングとラムダ式の人気の高まりに伴い、匿名オブジェクトと追加のメソッド呼び出しに関するオーバーヘッドの問題が発生しました。Javaでは、関数型インターフェースを使用することで同様の問題が見られます。Kotlinでは、ラムダ関数の渡し時に余分なアロケーションを避けるために、inlineというキーワードが導入されました。
問題:
ラムダパラメータを持つ関数の呼び出しは、ヒープに匿名オブジェクトを作成し、スタックの深さを増加させるため、コードの実行が遅くなります。特に、ループやネスト呼び出しで頻繁に使用される場合に顕著です。
解決策:
Kotlinでは、inline修飾子を使用して関数を宣言でき、その後、関数の本体と渡されたラムダが呼び出しの場所に直接挿入されるため、コンパイル時に余分なアロケーションを回避できます。これにより、特に短くて頻繁に呼び出される関数(例えば、コレクションのフィルタリング)に対してパフォーマンスが向上します。
コード例:
inline fun <T> Iterable<T>.myFilter(predicate: (T) -> Boolean): List<T> { val result = mutableListOf<T>() for (item in this) if (predicate(item)) result.add(item) return result } val filtered = listOf(1, 2, 3, 4).myFilter { it % 2 == 0 } println(filtered) // [2, 4]
主な特徴:
noinlineおよびcrossinlineパラメータの使用が可能。任意の機能パラメータやジェネリック型Tに対してインライン関数を使用できますか?
いいえ、インライン関数はまずラムダパラメータの呼び出しオーバーヘッドを節約しますが、ジェネリックパラメータ自体(例えばT)はインライン化されません。そのためには、reified修飾子が必要です。reifiedなしの単純なTの場合、型情報はコンパイル時に消去されます。
インライン関数内で外部スコープの変数にクロージャを宣言した場合、どうなりますか?
外部スコープの変数はインライン化された表現の内部にコピーされます。それらへのすべてのアクセスは、実際にコードが挿入されたかのように機能します。これにより、副作用を期待していない場合に予期しない動作が発生する可能性があります。
Javaコードからインライン関数を呼び出すことはできますか?
はい、ただし通常の関数としてコンパイルされるため、Javaコードはインライン化の利点を享受できません。Kotlinコードからインライン関数を使用することでのみ最適化が可能です。
returnの使用時のエラー — 流れの制御が不正確開発者がフィルタリングとポストプロセス用の複数のラムダを含む長い関数をインラインにし、速度の向上を期待しました。コードのコンパイルには時間がかかり、最終的なAPK/RAR/DEXは大幅に増加し、速度の向上はありませんでした。
メリット:
デメリット:
短い配列フィルタ用に小さなインライン関数が実装され、ホットループ内で何百回も呼び出され、実行時間が非常に重要です。ラムダが匿名オブジェクトを作成しないため、メモリのアロケーションは最小限です。
メリット:
デメリット: