rav1d ビデオデコーダの性能改善
(ohadravid.github.io)- Rustで書かれた rav1d AV1デコーダが、Cベースの dav1d と比べて約9%遅いことが判明
- バッファ初期化の最適化 と構造体比較ロジックの改善により、それぞれ個別に 1.5%、0.7% の高速化効果を確認
- プロファイリングツール samply を活用し、2つのバージョンの性能差の原因を具体的に特定
- Rustの PartialEq のデフォルト実装の代わりにバイト単位比較方式を用いて効率を向上
- 今回の最適化で全体の性能差の約30%を改善したが、まだ最適化の余地が残っている
背景とアプローチ
- rav1d は、dav1d AV1デコーダを c2rust で Rust に移植し、asm 最適化関数と Rust 言語特有の安全性改善を反映したプロジェクト
- 公開されたベースライン性能指標が定められており、Rustベースの rav1d は Cベースの dav1d より約5%遅い状態
- 複雑なビデオデコーダ全体の構造ではなく、同一入力におけるバイナリ実行時間の差に焦点を当てて分析
- 性能測定ツール(hyperfine) とプロファイラ(samply) で体系的に比較
- 対象環境は macOS M3 チップで、単一スレッド実行に単純化
性能測定: 初期値の比較
- 同一のテストファイル(Chimera-AV1-8bit-1920x1080-6736kbps.ivf)でそれぞれビルドとベンチマークを実施
- rav1d: 約73.9秒、dav1d: 約67.9秒で、約6秒(9%)の実行時間差を確認
- 各コンパイラ(Clang, Rustc) はほぼ同一の LLVM バージョンを使用
プロファイリング分析
- samply プロファイラで各実行ファイルの関数単位サンプル数を比較
- NEON(ARM SIMD) ベースのアセンブリ関数の呼び出し経路とサンプル分布を重点的に確認
- dav1d は別個のフィルタ関数に分離して asm 関数を分岐呼び出しし、rav1d は1つのディスパッチ関数で全てを管理
- cdef_filter_neon_erased 関数の Self サンプル数は、dav1d の2関数の合計より約270個多く、全体の1%に相当する差が見られた
- 分析の結果、一時バッファ(zero-initialized buffer) が不必要に大きく初期化されている区間を特定
バッファ初期化削減の最適化
- Rust は安全性のため、[0u16; LEN] のような形で自動的に zeroing を行う
- しかし C(dav1d) はバッファを明示的に zeroing せず、実際に使う区間にだけ値を書き込む
- Rust では std::mem::MaybeUninit を使って不要な初期化コストを削除
- cdef_filter_neon_erased 関数の Self サンプルは 670 個から 274 個へ大きく減少
- もう1つの大容量 Align16 バッファも、初期化をループの外に hoist して初期化コストを1回に削減
- 最適化後のベンチマークは約72.6秒となり、1.2秒(1.5%)改善
構造体比較の最適化
- プロファイリングの inverted stack 分析で、add_temporal_candidate 関数が予想以上に非効率に動作していることを発見
- この関数内の Mv 構造体のフィールド比較(PartialEq 自動実装) が、不必要に遅いコードを生成していた
- C では union を使って uint32_t 単位の効率的な比較を実施
- Rust では unsafe を避けつつ、zerocopy::AsBytes トレイトでバイトスライス単位の比較を実装
- この最適化でもさらに 0.5秒(約0.7%) の性能向上を達成
結果とまとめ
- 2つのシンプルな最適化(バッファ初期化削減、構造体のバイト比較) により、実行時間を約2%以上短縮
- それでもなお約6%の性能差が残っており、追加の最適化余地は大きい
- プロファイラのスナップショット比較手法が効果的であることを確認
- rav1d と dav1d のスナップショット分析に基づく追加最適化の可能性は高い
- プロジェクトメンテナたちの積極的なフィードバックと協力により、安全性を損なわずに改善を実現
要約
- プロファイラ(samply) とベンチマーク(hyperfine) ツールを使い、rav1d と dav1d の6秒(9%)の実行時間差を精密に分析
- 主な最適化は2つ:
- ARM 特化コードで不要なバッファ zeroing を削除(1.2秒, -1.6%)
- 小さな数値構造体の PartialEq 実装を高速なバイト比較に変更(0.5秒, -0.7%)
- 新たな unsafe コードなしで、各最適化は数十行以内に収まる簡潔なもの
- メンテナとの協業と PR レビューを通じて、信頼性と品質の向上も同時に達成
- なお約6%の性能差が残っており、さらなる profiler ベースの比較最適化を研究する余地は十分にある
ぜひ試してみてほしい! rav1d が最終的に dav1d より速くなるかもしれない 👀🦀。
1件のコメント
Hacker Newsのコメント
u16を2つ比較する問題が興味深いテーマだという意見が共有され、関連イシューへのリンクが示されている https://github.com/rust-lang/rust/issues/140167-O3でのコード生成結果はやりすぎだが-O2では妥当な判断だという見方、構造体の一方が演算直後であれば 32ビット読み込みを試みた際に store forwarding が失敗し、性能向上が無意味になり得るという具体的な説明、非インライン・非 PGO の状況ではコンパイラが最適化の適否判断に必要な情報を欠いているという指摘