Peningkatan kinerja dekoder video rav1d
(ohadravid.github.io)- Dekoder AV1 rav1d yang ditulis dengan Rust ditemukan sekitar 9% lebih lambat dibandingkan dav1d berbasis C
- Melalui optimasi inisialisasi buffer dan perbaikan logika perbandingan struct, masing-masing terkonfirmasi memberi peningkatan kecepatan sebesar 1,5% dan 0,7%
- Dengan memanfaatkan alat profiling samply, penyebab perbedaan performa antara dua versi dapat diidentifikasi secara spesifik
- Alih-alih implementasi bawaan PartialEq di Rust, efisiensi ditingkatkan dengan metode perbandingan per byte
- Optimasi kali ini memperbaiki sekitar 30% dari keseluruhan selisih performa, tetapi masih ada ruang untuk optimasi lebih lanjut
Latar belakang dan pendekatan
- rav1d adalah proyek yang mem-porting dekoder AV1 dav1d ke Rust dengan c2rust, lalu menambahkan fungsi optimasi asm serta peningkatan keamanan khas bahasa Rust
- Tolok ukur performa dasar telah ditetapkan secara publik, dan rav1d berbasis Rust berada sekitar 5% lebih lambat daripada dav1d berbasis C
- Alih-alih membahas struktur umum dekoder video yang kompleks, analisis difokuskan pada perbedaan runtime biner untuk input yang sama
- Perbandingan dilakukan secara sistematis dengan alat pengukur performa (hyperfine) dan profiler (samply)
- Lingkungan pengujian adalah chip macOS M3, disederhanakan dengan eksekusi single-thread
Pengukuran performa: perbandingan dasar
- Build dan benchmark dijalankan masing-masing dengan file uji yang sama (Chimera-AV1-8bit-1920x1080-6736kbps.ivf)
- rav1d: sekitar 73,9 detik, dav1d: sekitar 67,9 detik, sehingga terkonfirmasi ada selisih waktu eksekusi sekitar 6 detik (9%)
- Masing-masing compiler (Clang, Rustc) menggunakan versi LLVM yang nyaris sama
Analisis profiling
- Dengan profiler samply, jumlah sampel per fungsi pada masing-masing executable dibandingkan
- Jalur pemanggilan dan distribusi sampel dari fungsi assembly berbasis NEON (ARM SIMD) diperiksa secara fokus
- dav1d memisahkan pemanggilan fungsi asm melalui fungsi filter terpisah, sedangkan rav1d mengelola semuanya lewat satu fungsi dispatch
- Pada fungsi
cdef_filter_neon_erased, jumlah sampel Self terlihat sekitar 270 lebih banyak dibandingkan gabungan dua fungsi di dav1d (setara sekitar 1% dari total) - Hasil analisis menangkap bagian di mana buffer sementara (zero-initialized buffer) diinisialisasi terlalu besar secara tidak perlu
Optimasi penghapusan inisialisasi buffer
- Demi keamanan, Rust melakukan zeroing otomatis dengan cara seperti [0u16; LEN]
- Namun C (dav1d) tidak melakukan zeroing buffer secara eksplisit, dan hanya menulis nilai pada area yang benar-benar digunakan
- Di Rust, biaya inisialisasi yang tidak perlu dihilangkan dengan menggunakan std::mem::MaybeUninit
- Sampel Self pada fungsi
cdef_filter_neon_erasedturun drastis dari 670 menjadi 274 - Buffer besar
Align16lainnya juga dipindahkan inisialisasinya ke luar loop, sehingga biaya inisialisasi berkurang menjadi satu kali saja - Setelah optimasi, benchmark menjadi sekitar 72,6 detik, membaik 1,2 detik (1,5%)
Optimasi perbandingan struct
- Dari analisis inverted stack pada profiling, ditemukan bahwa fungsi add_temporal_candidate bekerja lebih tidak efisien dari perkiraan
- Pada perbandingan field struct Mv di dalam fungsi ini (implementasi otomatis PartialEq), dihasilkan kode yang tidak perlu lambat
- Di C, perbandingan efisien dilakukan per unit
uint32_tdengan menggunakan union - Di Rust, tanpa menghindari
unsafe, perbandingan per byte pada level byte slice diimplementasikan dengan trait zerocopy::AsBytes - Optimasi ini kembali menghasilkan peningkatan performa 0,5 detik (sekitar 0,7%)
Hasil dan ringkasan
- Dua optimasi sederhana (penghapusan inisialisasi buffer dan perbandingan struct per byte) menghasilkan pemangkasan runtime lebih dari sekitar 2%
- Masih tersisa selisih performa sekitar 6%, dan ruang optimasi tambahan masih besar
- Dipastikan bahwa metode perbandingan antar snapshot profiler sangat efektif
- Potensi optimasi lanjutan berbasis analisis snapshot rav1d dan dav1d dinilai tinggi
- Berkat umpan balik aktif dan kerja sama dari para maintainer proyek, perbaikan berhasil dicapai tanpa mengorbankan keamanan
Ringkasan
- Selisih runtime 6 detik (9%) antara rav1d dan dav1d dianalisis secara presisi dengan alat profiler (samply) dan benchmark (hyperfine)
- Dua optimasi utama:
- Menghapus zeroing buffer yang tidak perlu pada kode khusus ARM (1,2 detik, -1,6%)
- Mengubah implementasi PartialEq untuk struct numerik kecil menjadi perbandingan byte yang lebih cepat (0,5 detik, -0,7%)
- Masing-masing optimasi tetap ringkas, dalam hitungan beberapa puluh baris, tanpa kode
unsafebaru - Melalui kolaborasi maintainer dan review PR, keandalan dan kualitas berhasil ditingkatkan sekaligus
- Karena masih ada kesenjangan performa sekitar 6%, ruang untuk riset optimasi lanjutan berbasis perbandingan profiler masih sangat terbuka
Silakan coba sendiri! Mungkin suatu hari rav1d bisa menjadi lebih cepat daripada dav1d 👀🦀.
1 komentar
Opini Hacker News
u16adalah topik yang menarik, serta memberikan tautan isu terkait https://github.com/rust-lang/rust/issues/140167store forwardingtidak disebut dalam diskusi; hasil generasi kode pada-O3memang berlebihan, tetapi pada-O2masih masuk akal. Dijelaskan secara spesifik bahwa bila salah satu struktur baru saja dipakai dalam operasi, percobaanload32-bit bisa gagal melakukanstore forwardingsehingga peningkatan performa menjadi tidak berarti. Juga ditunjukkan bahwa dalam kondisi non-inline/non-PGO, kompiler kekurangan informasi yang dibutuhkan untuk menilai kelayakan optimasizeroing) muncul beberapa hari setelah tulisan terkait sebelumnya, sambil membagikan tautan tulisan lama https://news.ycombinator.com/item?id=44032680aarch64, jadi agak kurang adil bila disebut sebagai angka keseluruhan; dengan mempertimbangkan proporsi arm/x86, angka yang lebih tepat mungkin sekitar setengahnyadav1dke WUFFS jauh lebih sulit daripada sekadar menerjemahkan dan merapikan kode C yang ada. Meski begitu, upaya seperti ini tetap dianggap bernilai dan layak didanai sebagai investasi peradabanwebm, danmp4, tetapi sama sekali tidak cocok untuk video decoder. Karena tidak ada alokasi memori dinamis, penanganan data dinamis menjadi menantang; ditekankan bahwa codec video bukan sekadar parsing file, melainkan membutuhkan pengelolaan state dinamis yang sangat beragamNominative determinism(efek penamaan)perfdengan baik; isuzeroingpun dikira sudah dibahas di postingan pertama. Optimasi kedua dianggap lebih rumit dan menarik, tetapi tetap ditekankan bahwa arahnya juga dipandu olehperf; disarankan agar kegunaan toolperftidak diremehkanperf, tetapi juga melalui proses differential profiling antara versi C dan versi Rust serta pencocokan manual. Disebutkan bahwa fiturperf diffmemang ada, tetapi perbedaan nama simbol membuat pencocokan otomatis sulit dilakukanaarch64; dari pengalaman, orang dengan latar belakang berbeda bisa lebih cepat menangkap hal-hal yang belakangan terasa “sudah jelas”dav1d; digunakan analogi olahraga bahwa sekadar memperbaiki catatan waktu terasa kurang mengesankan dibanding benar-benar mencetak rekor baru. Penjelasan penutupnya bernada ringan: solusi yang sesungguhnya adalah hasil yang benar-benar cepat dan inovatif