概要
- macOS M3チップ環境で、Rust製rav1dの高速化に取り組んだ事例を解説
- 安全性を損なわず、バッファ初期化の最適化で約1%の性能向上を実現
- C実装(dav1d)とのプロファイリング比較でボトルネックを特定
MaybeUninit活用やバッファ使い回しによる無駄なゼロクリアの削減を提案- 細かな改善でも着実なパフォーマンス向上が得られることを確認
Rust製rav1dのパフォーマンス改善事例:macOS M3での検証
背景とアプローチ
- Rust製AV1デコーダrav1dは、C実装dav1dをc2rustで変換し、アセンブリ最適化関数を組み込み、安全性を高めた設計を採用すること
- メモリ安全性を損なわずにC実装に近づける性能改善を目指すこと
- 比較対象は、Rust版がC版より約5〜9%遅いという既存ベンチマーク結果であること
- aarch64(M3環境)はx86_64より最適化が進んでいない可能性が高いこと
- サンプリングプロファイラ(samply)で両実装の関数ごと性能差を分析すること
ベースライン測定
- rav1dとdav1dを同一入力ファイル・同一スレッド数でビルド・ベンチマーク実施すること
- hyperfineで実行時間を測定し、M3チップ上でrav1dがdav1dより約9%遅いことを確認すること
- clang/rustcのLLVMバージョン差は許容範囲内で評価すること
プロファイリングとボトルネック特定
- samplyでプロファイリングを行い、アセンブリ関数(cdef_filter_8x8_neon等)を「アンカー」として比較すること
- Rust版のcdef_filter_neon_erased関数でSelfサンプル数がC版より大きいことを発見すること
- cdef_filter_neon_erasedの内部で、[u16; 200]バッファを毎回ゼロ初期化していることが性能低下の要因であることを特定すること
- C実装ではこのバッファは未初期化で、パディング関数の書き込み先としてのみ使用されていることを確認すること
Rustでのバッファ初期化最適化
- Rustのstd::mem::MaybeUninitを使い、不要なゼロ初期化を回避する提案
let mut tmp_buf = Align16([MaybeUninit::<u16>::uninit(); TMP_LEN]);で未初期化バッファを確保すること- 内部関数のシグネチャもMaybeUninit対応に変更すること
- 既存unsafeコード範囲内での変更のため、新たなunsafe追加は不要であることを確認すること
- プロファイリングの再実行で、Selfサンプル数が大幅に減少(670→274)し、C実装に近づくことを確認すること
その他の微細な最適化
- もう一つの大きなAlign16バッファ(lr_bak)も初期化コスト削減の対象とすること
- ループ外で一度だけ初期化し、使い回すことで無駄なゼロクリアを減らすこと
- C実装と同様、未初期化でも安全に使えるロジックであることを確認すること
- この改善は小さいが、積み重ねが全体性能に寄与することを強調すること
まとめ
- RustでC並みの性能を目指すには、メモリ初期化のコストに注意し、
MaybeUninitなどの活用で無駄なゼロクリアを削減する提案 - プロファイリングにより関数単位でボトルネックを特定し、ピンポイントで改善することが重要であること
- 小さな最適化でも積み重ねることで、着実なパフォーマンス向上を実現できることを確認すること
- 安全性を損なわずにRustコードの効率化を図る場合、C実装の挙動と比較しながら最適化することが有効であること