Androidのビルドツールが、Kotlin Coroutinesの高速化を実現しました。Android Gradle Plugin (AGP) 9.2.0と、そこに同梱されているR8 shrinkerにより、コルーチンの状態管理を担うバイトコードが書き換えられ、最も一般的なコルーチン操作において約2倍のパフォーマンスを実現します。コルーチンを利用するAndroidアプリを開発しているエンジニアは、Kotlinコードを一行も変更することなく、コールドスタート時間の短縮、フレームレートの安定化、そしてバッテリー持ちのわずかな改善を享受できます。

なぜコルーチンのパフォーマンスが重要だったのか

Kotlin Coroutinesは、複数のスレッドから内部フィールドを安全に操作するためにAtomicFieldUpdaterオブジェクトを使用しています。この手法は、更新のたびに新しいアトミックオブジェクトを割り当てることを回避しますが、Android上では3つの隠れたコストが発生します。

  • クラスロードの遅延 – アップデーターがリフレクションによって作成されるため、VMは実行時にターゲットとなるフィールドをルックアップする必要があります。
  • CPUの浪費 – 各アクセスにおいて、実際のフィールドに到達する前にアップデーターの状態を確認する必要があります。
  • インライン化の制限 – コンパイラがアップデーターの呼び出しをインライン化できないため、生成されるバイトコードが大きくなり、速度が低下します。

実際、これらのコストは、channelのsend/receive、mutexのlock/unlock、StateFlowの更新、コルーチンのディスパッチといったホットパスにおいて表面化します。その結果、アプリ本来の処理ではなく、管理処理(bookkeeping)にかなりのCPU時間が費やされることになります。

AGP 9.2.0とR8で何が変わったのか

AGP 9.2.0に同梱されているコードシュリンカーであるR8は、コンパイルされたバイトコードをスキャンして、標準的なAtomicFieldUpdaterパターンを探すようになりました。パターンが見つかった場合、R8は以下の4つの変換を行います。

  1. フィールドのメモリオフセットを特定する。 R8は、オブジェクトレイアウト内におけるターゲットフィールドの正確な位置を計算します。
  2. アップデーターオブジェクトを削除する。 リフレクションによるラッパーが消滅し、メモリが節約され、クラスロードの作業が排除されます。
  3. 直接的な sun.misc.Unsafe 呼び出しを挿入する。 この低レベルAPIは、単一のアトミックなハードウェア命令を使用してフィールドに書き込みます。
  4. すべてのアップデーター呼び出しを新しいunsafe命令に置き換える。 これにより、JITコンパイラが操作をインライン化できるようになります。

その結果、CPUはリフレクションによるルックアップや実行時のチェックを行う必要がなくなり、アトミック命令を直接実行できるようになります。開発者の視点からは、コルーチンAPIの挙動は変わらないため、この変更は目に見えません。しかし、内部ではコードが「ハードウェアに近いレベル」の速度で動作しています。

測定可能な利点

一般的なAndroidデバイスでのベンチマークでは、AGP 9.2.0を使用し、R8を有効にして、minification(難読化・最適化)をオンにしてビルドした場合、以下の高速化が示されています。

  • Channelのsend/receive: 2.01倍高速化
  • Mutexのlock/unlock: 1.90倍高速化
  • StateFlowの更新: 2.02倍高速化
  • コルーチンのディスパッチ: 1.68倍高速化

これらの数値は、具体的なユーザーエクスペリエンスの向上につながります。コルーチンの同期待ちでコンマ数秒を費やしていたコールド起動がより早く完了するようになり、UIスレッドが最初のフレームをレンダリングするための余裕が生まれます。また、CPUの競合が減ることで、プロセッサがより早くスリープ状態に戻れるようになり、バッテリー寿命の改善も期待できます。

利点を享受する方法

コードの変更は一切必要ありません。この書き換えを有効にするには、以下が必要です。

  • AGP 9.2.0以降 – 更新されたR8が含まれているバージョン。
  • R8 – 上記のAGPを使用してビルドする際に自動的に使用されます。
  • Kotlin Coroutines 1.8.0以降 – 最適化エンジンが期待するAtomicFieldUpdaterパターンを備えたライブラリバージョン。
  • releaseビルドタイプでの isMinifyEnabled = true の設定 – R8はminificationが有効な場合にのみ実行されます。

唯一の追加作業は、ProGuard(またはR8)ルールの監査です。volatileフィールドやアップデータークラス自体を保持するような広範な-keepディレクティブは、この書き換えを妨げます。ルールがR8によるフィールドの変更を許可していることを確認してください。そうでない場合、最適化エンジンは元のリフレクションによる実装にフォールバックしてしまいます。

この変換は、Android StudioのAPK Analyzerを使用して確認できます。コンパイルされたAPKを開き、JobSupportなどのコルーチンサポートクラスを探して、デコンパイルされたバイトコードを検査してください。書き換えが成功していれば、静的なアップデーターフィールドは存在せず、代わりにUnsafeへの直接的な呼び出しが見えるはずです。

注意事項と反論

この最適化は、すべてのプロジェクトが満たしているわけではない2つの条件に基づいています。

  1. Minificationを有効にする必要があります。 デバッグビルド、あるいはデバッグの利便性のためにMinificationをオフにしたままのリリースビルドでは、その恩恵を得ることはできません。
  2. ProGuardルールを寛容にする必要があります。 coroutineの内部実装に対して厳格な -keep パターンを使用しているプロジェクトでは、それらのルールを緩和する必要があるかもしれません。注意深くテストしない場合、内部クラスがコードの縮小(shrinking)に関連するバグの影響を受ける可能性があります。

ターゲットとする一連のデバイスでテストを行うことが、引き続き推奨されるプラクティスです。

次に注目すべき点

今回の書き換えは、ビルド時のバイトコード変換によって、言語の抽象化の背後に隠れたパフォーマンスをいかに引き出せるかを示しています。特定のワークロードにおける向上を確認するために、coroutineを多用する自身のコードをプロファイリングしてみることを検討してください。

要点: AGP 9.2.0へのアップグレードとR8のMinificationを有効にすることで、ビルド設定がオプティマイザの動作を妨げない限り、ソースコードを変更することなく、Kotlinのcoroutineを多用するアプリの重要な同期速度をほぼ2倍に向上させることができます。