rav1d वीडियो डिकोडर का परफ़ॉर्मेंस सुधार
(ohadravid.github.io)- Rust में लिखा गया rav1d AV1 डिकोडर, C-आधारित dav1d की तुलना में लगभग 9% धीमा पाया गया
- बफ़र initialization optimization और struct comparison logic में सुधार से क्रमशः 1.5% और 0.7% की स्पीड बढ़ोतरी देखी गई
- profiling tool samply का उपयोग करके दोनों versions के performance gap के कारणों को विस्तार से पहचाना गया
- Rust के डिफ़ॉल्ट PartialEq implementation की जगह byte-level comparison का उपयोग करके efficiency बढ़ाई गई
- इस optimization से कुल performance gap का लगभग 30% सुधरा, लेकिन अभी भी optimization की गुंजाइश बाकी है
पृष्ठभूमि और अप्रोच विधि
- rav1d, dav1d AV1 डिकोडर को c2rust के जरिए Rust में port करके, asm optimized functions और Rust-विशिष्ट safety improvements को शामिल करने वाला प्रोजेक्ट है
- सार्वजनिक रूप से एक baseline performance target तय है, और Rust-आधारित rav1d, C-आधारित dav1d से लगभग 5% धीमा है
- जटिल वीडियो डिकोडर की समग्र संरचना के बजाय, समान input पर binary runtime के अंतर पर ध्यान केंद्रित करके विश्लेषण किया गया
- performance measurement tool (hyperfine) और profiler (samply) से व्यवस्थित तुलना की गई
- target environment macOS M3 chip था, और single-thread execution से इसे सरल बनाया गया
performance measurement: default comparison
- एक ही test file (Chimera-AV1-8bit-1920x1080-6736kbps.ivf) पर अलग-अलग build और benchmark चलाए गए
- rav1d: लगभग 73.9 सेकंड, dav1d: लगभग 67.9 सेकंड, यानी लगभग 6 सेकंड (9%) का runtime अंतर पाया गया
- दोनों compilers (Clang, Rustc) लगभग समान LLVM version का उपयोग करते हैं
profiling analysis
- samply profiler से प्रत्येक executable के function-level sample counts की तुलना की गई
- NEON (ARM SIMD) आधारित assembly functions के call paths और sample distribution पर विशेष ध्यान दिया गया
- dav1d, asm functions को branch-call करने के लिए अलग filter functions का उपयोग करता है, जबकि rav1d एक dispatch function में सब कुछ संभालता है
cdef_filter_neon_erasedfunction के Self samples, dav1d के दो functions के कुल sample count से लगभग 270 अधिक निकले (कुल का 1%)- विश्लेषण में यह सामने आया कि temporary buffer (zero-initialized buffer) अनावश्यक रूप से बहुत बड़ा initialize किया जा रहा था
buffer initialization removal optimization
- Rust, safety के लिए [0u16; LEN] जैसी शैली से अपने-आप zeroing करता है
- लेकिन C (dav1d) बफ़र को स्पष्ट रूप से zeroing नहीं करता, और वास्तव में उपयोग होने वाले हिस्सों में ही value लिखता है
- Rust में std::mem::MaybeUninit का उपयोग करके अनावश्यक initialization cost हटाई गई
cdef_filter_neon_erasedfunction के Self samples 670 से घटकर 274 रह गए- एक अन्य बड़े Align16 buffer के लिए भी initialization को loop के बाहर hoist करके cost को एक बार तक सीमित किया गया
- optimization के बाद benchmark लगभग 72.6 सेकंड रहा, यानी 1.2 सेकंड (1.5%) का सुधार हुआ
struct comparison optimization
- profiling के inverted stack analysis में पता चला कि add_temporal_candidate function अपेक्षा से कम efficient तरीके से काम कर रहा था
- इस function में Mv struct के field comparison (PartialEq auto implementation) से अनावश्यक रूप से धीमा code बन रहा था
- C में union का उपयोग करके
uint32_tस्तर पर efficient comparison किया जाता है - Rust में unsafe से बचते हुए zerocopy::AsBytes trait के साथ byte slice स्तर पर comparison लागू किया गया
- इस optimization से फिर 0.5 सेकंड (लगभग 0.7%) का performance improvement मिला
परिणाम और निष्कर्ष
- दो सरल optimizations (buffer initialization हटाना, struct byte comparison) से runtime में 2% से अधिक की कमी हासिल हुई
- अभी भी लगभग 6% का performance gap बाकी है, और आगे optimization की गुंजाइश काफ़ी है
- profiler snapshots के बीच तुलना करने की विधि प्रभावी साबित हुई
- rav1d और dav1d के snapshot analysis के आधार पर आगे और optimization की संभावना अधिक है
- project maintainers के सक्रिय feedback और सहयोग से safety से समझौता किए बिना सुधार संभव हुआ
सारांश
- profiler (samply) और benchmark (hyperfine) tools की मदद से rav1d और dav1d के बीच 6 सेकंड (9%) के runtime gap का सटीक विश्लेषण किया गया
- दो मुख्य optimizations:
- ARM-विशिष्ट code में अनावश्यक buffer zeroing हटाना (1.2 सेकंड, -1.6%)
- छोटे numeric struct की PartialEq implementation को तेज byte comparison में बदलना (0.5 सेकंड, -0.7%)
- नया unsafe code जोड़े बिना, हर optimization कुछ दर्जन lines के भीतर संक्षिप्त रहा
- maintainers के सहयोग और PR review के जरिए reliability और quality improvement दोनों हासिल किए गए
- अभी भी लगभग 6% का performance gap बचा है, इसलिए आगे profiler-आधारित comparative optimization research की काफी संभावना है
आगे बढ़िए और इसे आज़माइए! शायद rav1d कभी dav1d से भी तेज़ हो जाए 👀🦀.
1 टिप्पणियां
Hacker News टिप्पणियाँ
u16की तुलना वाला issue एक दिलचस्प विषय है, और संबंधित issue का लिंक दिया गया https://github.com/rust-lang/rust/issues/140167-O3में code generation का नतीजा कुछ ज़्यादा है, लेकिन-O2में यह उचित निर्णय लगता है। अगर किसी struct में से एक पर ऑपरेशन अभी-अभी हुआ हो, तो 32-bit load की कोशिश में store forwarding fail हो सकता है, जिससे performance gain बेअसर हो सकता है। non-inline/non-PGO स्थिति में compiler के पास optimization की उपयुक्तता तय करने के लिए ज़रूरी जानकारी कम होती है, यह बात भी उठाई गई