rav1d video çözücüsünde performans iyileştirmeleri
(ohadravid.github.io)- Rust ile yazılan rav1d AV1 çözücüsünün, C tabanlı dav1d'ye kıyasla yaklaşık %9 daha yavaş olduğu tespit edildi
- Arabellek başlatma optimizasyonu ve yapı karşılaştırma mantığındaki iyileştirmelerle, ayrı ayrı sırasıyla %1,5 ve %0,7 hız artışı sağlandığı doğrulandı
- Profil oluşturma aracı samply kullanılarak iki sürüm arasındaki performans farkının nedenleri somut biçimde belirlendi
- Rust'ın varsayılan PartialEq uygulaması yerine bayt düzeyinde karşılaştırma yöntemi kullanılarak verimlilik artırıldı
- Bu optimizasyonlarla toplam performans farkının yaklaşık %30'u giderildi, ancak hâlâ ek optimizasyon alanı bulunuyor
Arka plan ve yaklaşım
- rav1d, dav1d AV1 çözücüsünün c2rust ile Rust'a taşındığı ve asm optimizasyon fonksiyonları ile Rust diline özgü güvenlik iyileştirmelerinin uygulandığı bir proje
- Kamuya açık temel performans ölçütleri tanımlanmış durumda ve Rust tabanlı rav1d, C tabanlı dav1d'den yaklaşık %5 daha yavaş
- Karmaşık video çözücünün genel yapısı yerine, aynı girdi altında ikili çalışma süresi farkına odaklanan bir analiz yapıldı
- Performans ölçüm aracı (hyperfine) ve profiler (samply) ile sistemli bir karşılaştırma gerçekleştirildi
- Hedef ortam macOS M3 çipi ve analiz, sadeleştirme için tek iş parçacıklı çalıştırma üzerinde yapıldı
Performans ölçümü: varsayılan karşılaştırma
- Aynı test dosyasıyla (Chimera-AV1-8bit-1920x1080-6736kbps.ivf) her iki sürüm de derlenip kıyaslandı
- rav1d yaklaşık 73,9 saniye, dav1d ise yaklaşık 67,9 saniye sürdü; yani yaklaşık 6 saniyelik (%9) bir çalışma süresi farkı görüldü
- Her derleyici de (Clang, Rustc) neredeyse aynı LLVM sürümünü kullanıyor
Profil analizi
- samply profiler'ı ile her çalıştırılabilir dosyanın fonksiyon bazlı örnek sayıları karşılaştırıldı
- NEON (ARM SIMD) tabanlı assembly fonksiyonlarının çağrı yolları ve örnek dağılımı özellikle incelendi
- dav1d, asm fonksiyonlarını dallanarak çağırmak için ayrı filtre fonksiyonlarına ayrılırken, rav1d hepsini tek bir dispatch fonksiyonunda yönetiyor
cdef_filter_neon_erasedfonksiyonunun Self örnek sayısının, dav1d'deki iki fonksiyonun toplamından yaklaşık 270 daha fazla olduğu görüldü (toplamın yaklaşık %1'i)- Analiz sonucunda, geçici arabelleğin (zero-initialized buffer) gereksiz yere büyük ölçekte başlatıldığı bölüm yakalandı
Arabellek başlatmasını kaldırma optimizasyonu
- Rust, güvenlik amacıyla [0u16; LEN] gibi bir yöntemle otomatik zeroing uygular
- Ancak C tarafında (dav1d), arabellek açıkça zeroing yapılmadan bırakılır ve yalnızca gerçekten kullanılan alanlara değer yazılır
- Rust'ta std::mem::MaybeUninit kullanılarak gereksiz başlatma maliyeti kaldırıldı
cdef_filter_neon_erasedfonksiyonunun Self örnek sayısı 670'ten 274'e kadar ciddi biçimde düştü- Başka bir büyük
Align16arabelleğinde de başlatma işlemi döngünün dışına hoist edilerek bu maliyet tek seferle sınırlandı - Optimizasyon sonrasında benchmark sonucu yaklaşık 72,6 saniyeye indi; bu da 1,2 saniyelik (%1,5) iyileşme anlamına geliyor
Yapı karşılaştırma optimizasyonu
- Profildeki inverted stack analizi, add_temporal_candidate fonksiyonunun beklenenden daha verimsiz çalıştığını ortaya koydu
- Bu fonksiyon içinde Mv yapısının alan karşılaştırması (
PartialEqotomatik türetimi), gereksiz yere yavaş kod üretiyordu - C tarafında,
unionkullanılarakuint32_tdüzeyinde verimli bir karşılaştırma yapılıyor - Rust'ta ise
unsafekullanmaktan kaçınılarak zerocopy::AsBytes trait'i ile bayt dilimi düzeyinde karşılaştırma uygulandı - Bu optimizasyon da ek olarak 0,5 saniyelik (yaklaşık %0,7) performans artışı sağladı
Sonuç ve özet
- İki basit optimizasyonla (arabellek başlatmasının kaldırılması ve yapıların bayt düzeyinde karşılaştırılması) çalışma süresinde toplam %2'nin üzerinde kısalma sağlandı
- Buna rağmen hâlâ yaklaşık %6'lık bir performans farkı bulunuyor ve ek optimizasyon potansiyeli yüksek
- Profiler anlık görüntüleri arasında karşılaştırma yapma yaklaşımının etkili olduğu görüldü
- rav1d ve dav1d için snapshot analizine dayalı ek optimizasyon olasılığı yüksek
- Proje bakımcılarının aktif geri bildirimi ve iş birliği sayesinde güvenlikten ödün vermeden iyileştirme sağlandı
Özet
- Profiler (samply) ve benchmark (hyperfine) araçlarıyla rav1d ile dav1d arasındaki 6 saniyelik (%9) çalışma süresi farkı ayrıntılı biçimde analiz edildi
- İki ana optimizasyon:
- ARM'e özgü kodda gereksiz arabellek zeroing işleminin kaldırılması (1,2 saniye, -%1,6)
- Küçük sayısal yapıların
PartialEquygulamasının hızlı bayt karşılaştırmasıyla değiştirilmesi (0,5 saniye, -%0,7)
- Yeni
unsafekod eklenmeden, her optimizasyon birkaç düzine satır içinde sade biçimde uygulandı - Bakımcılarla iş birliği ve PR incelemeleri sayesinde hem güvenilirlik hem kalite iyileştirildi
- Hâlâ yaklaşık %6'lık performans farkı kaldığından, profiler tabanlı ek karşılaştırmalı optimizasyon çalışmaları için geniş alan bulunuyor
Hadi siz de deneyin! Belki rav1d bir gün dav1d'den daha hızlı bile olabilir 👀🦀.
1 yorum
Hacker News görüşleri
u16iki değerini karşılaştırma meselesinin ilginç bir konu olduğu paylaşılıyor; ilgili issue bağlantısı verilmiş: https://github.com/rust-lang/rust/issues/140167-O3altındaki kod üretiminin aşırıya kaçtığını ama-O2'de makul olduğunu, yapılardan birinin işlemden hemen sonra gelmesi halinde 32 bit yükleme denemesinin store forwarding başarısızlığına yol açarak performans kazanımını anlamsız kılabileceğini ayrıntılı biçimde açıklıyor; inline olmayan / PGO'suz durumlarda derleyicinin optimizasyon uygunluğunu değerlendirmek için gerekli bilgiye sahip olmadığını belirtiyoraarch64için geçerli olduğu, bu yüzden bunu genel toplam gibi söylemenin pek adil olmadığı; ARM/x86 oranı düşünülürse bunun yaklaşık yarısı olarak görülmesi gerektiği savunuluyordav1dkadar karmaşık kodu WUFFS'a dönüştürmenin mevcut C kodunu çevirmekten ve temizlemekten çok daha zor olduğu deneyimle paylaşılıyor; buna rağmen böyle bir girişimin değerli olduğu ve uygarlık düzeyinde yatırım yapılmayı hak ettiği savunuluyorMatroska,webm,mp4gibi konteynerleri parse etmek için uygun olduğu ama video decoder'lar için hiç uygun olmadığı açıklanıyor; dinamik bellek ayırma olmamasının dinamik veri işlemeyi zorlaştırdığı, video codec'lerinin yalnızca dosya parse etmekten ibaret olmayıp çok çeşitli dinamik durum yönetimi gerektirdiği vurgulanıyorrav1dödülünün ne durumda olduğunu merak edip kendi kendine soruyormuş gibi yazdığını, aynı şeyi düşünen başkalarının olmasına sevindiğini söylüyorperfdoğru kullanıldığında kolayca bulunabilecek yaygın bir tür olduğu için biraz şaşırtıcı olduğu söyleniyor; zeroing meselesinin ilk yazıda zaten tartışıldığını sandığını, ikinci optimizasyonun daha karmaşık ve ilginç olsa da yineperf'in yönlendirdiği bir keşif olduğunu vurguluyor;perfaracının faydasını küçümsememek gerektiğini öğütlüyorperfkullanılmadığını; C sürümü ile Rust sürümü arasında diferansiyel profiling ve manuel eşleştirme yapılarak sonuca ulaşıldığını netleştiriyor;perf diffözelliği olsa da sembol adları farklı olduğu için otomatik eşleştirmenin zor olduğuna dikkat çekiyoraarch64tabanlı Apple cihazları perspektifinden geldiğini belirtiyor; farklı geçmişlerden gelen insanların, sonradan dönüp bakınca “çok bariz” görünen noktaları bile hızlı yakalayabildiğini deneyimiyle vurguluyordav1dperformansını iyileştirerek verilmesi olduğu öneriliyor; spor rekorları benzetmesiyle, sadece dereceleri biraz iyileştirmenin gerçek bir dünya rekoru kırmak kadar etkileyici olmadığını söylüyor; asıl çözümün gerçekten daha hızlı ve yenilikçi sonuçlar üretmek olduğu neşeli bir dille anlatılıyor