Async RustはMVP状態を脱却できなかった
概要
- async Rust のバイナリサイズ増大(bloat)の根本的な課題
- コンパイラ最適化によるbloat解消の提案と実験結果
- Futureの状態管理やpanic処理、状態機械の仕組み解説
- 手動最適化・inliningによるバイナリサイズ削減の可能性
- 今後の最適化案やコミュニティへの協力呼びかけ
async Rustのバイナリ肥大化問題とコンパイラ改善提案
- async Rustは実行環境を選ばず、サーバーからマイコンまで幅広く利用可能な並行処理技術
- 特に組み込み機器などメモリ制約が厳しい環境では、asyncによるバイナリサイズ増大が深刻な問題
- 既存のワークアラウンド(回避策)では根本解決に至らず、コンパイラレベルでの最適化が望ましい状況
- Project Goalを提出し、開発資金の支援を募集中
async Futureの内部構造と生成コードの課題
- async関数は**状態機械(state machine)**としてMIR(Mid-level IR)上で変換・管理
- 例としてbar関数では2つのawaitポイントごとに異なる状態が生成され、MIR出力は360行に及ぶ
- 状態はUnresumed, Returned, Panicked, Suspend0, Suspend1など複数存在
- Returned/Panicked状態はpoll後の再呼び出しやpanic後の再利用を防止するために設計
- パニック処理はコストが高く、バイナリ肥大化や最適化阻害の要因
パニック処理の見直しによるバイナリ削減案
- poll後にパニックではなくPendingを返すことで、2〜5%のバイナリサイズ削減を確認
- デバッグビルドでは従来通りパニック、リリースビルドではサイズ重視の挙動切替を提案
- panic=abort時はPanicked状態自体を省略できる可能性も検討
シンプルなFutureの最適化例
- 例:
async { 5 }のような即時完了Futureは状態管理不要- 手動実装では常に
Poll::Ready(5)を返すだけで十分
- 手動実装では常に
- 現状のコンパイラ生成コードは不要な状態管理・分岐が含まれ、最適化余地あり
- この単純最適化でも0.2%のバイナリ削減を確認
LLVM最適化の限界と入力品質の重要性
- LLVMは最適化レベル3であれば一部不要コードを除去できるが、複雑なFutureでは困難
- パニック分岐があるとLLVMが最適化しきれず、呼び出しや状態管理が残存
- MIR段階での最適化が不可欠
Futureのインライン化とさらなる最適化案
- Rustのasync Futureは自動でインライン化されないため、状態機械がネストしてしまいバイナリ肥大
- 例:
barが単にfoo().awaitする場合、bar独自の状態機械を持つのは非効率 - 前処理・後処理を含む場合も、状態をうまく共有すれば状態数削減が可能
- Futureの特性(即時Readyなど)をコンパイラが認識できれば、さらなる最適化が可能
状態の統合(state collapsing)による重複削減
- 複数のawaitが本質的に同じ状態を持つ場合、状態を統合することで重複コード削減
- 例:
match分岐ごとにawaitする場合、awaitの前に分岐し、1つのawaitにまとめることでMIRが大幅に短縮let response = match ...; send_response(response).await;のような書き換え
今後の展望とコミュニティへの協力要請
- async bloatの根本解決には、コンパイラの抜本的な最適化が不可欠
- 手動最適化やワークアラウンドだけでは限界があるため、ツールチェーン側の対応が重要
- 資金援助やアイデア提供など、Rustコミュニティの協力を呼びかけ
この内容はasync Rustのバイナリサイズ最適化に関心のある開発者や、組み込み用途でRustを利用するエンジニアにとって有用な知見です。