rav1d 비디오 디코더 성능 개선

18 hours ago 2

  • Rust로 작성된 rav1d AV1 디코더가 C 기반 dav1d에 비해 약 9% 느린 점을 발견함
  • 버퍼 초기화 최적화와 구조체 비교 로직 개선으로 개별적으로 각각 1.5%, 0.7%의 속도 향상 효과를 확인함
  • 프로파일링 도구 samply를 활용해 두 버전의 성능 차이 원인을 구체적으로 파악함
  • 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는 하나의 디스패치 함수로 모두 관리함
  • cdef_filter_neon_erased 함수의 Self 샘플 수가 dav1d의 두 함수의 합보다 약 270개 더 많은 차이가 나타남(전체 1%에 해당)
  • 분석 결과, 임시 버퍼(zero-initialized buffer) 가 불필요하게 크게 초기화되는 구간을 포착함

버퍼 초기화 제거 최적화

  • Rust는 안전을 위해 [0u16; LEN] 와 같은 방식으로 자동 zeroing을 진행함
  • 하지만 C(dav1d)는 버퍼를 명시적으로 zeroing하지 않고, 실제로 사용하는 구간만 값을 씀
  • Rust에서 std::mem::MaybeUninit을 사용해 불필요한 초기화 비용을 제거함
  • cdef_filter_neon_erased 함수의 Self 샘플이 670개에서 274개로 크게 줄어듦
  • 또 다른 대용량 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% 이상의 런타임 단축을 실현함
  • 여전히 약 6% 정도의 성능 차이가 남아있으며, 추가 최적화 여지가 큼
  • 프로파일러 스냅샷 간 비교 방식이 효과적임을 확인함
  • rav1d와 dav1d의 스냅샷 분석 기반 추가 최적화 가능성 높음
  • 프로젝트 유지관리자들의 적극적 피드백과 협조로 안전성을 해치지 않으면서 개선 실현함

요약

  • 프로파일러(samply)와 벤치마크(hyperfine) 도구를 이용해 rav1d와 dav1d의 6초(9%) 런타임 차이를 정밀 분석함
  • 주요 최적화 두 가지:
    • ARM 특화 코드에서 불필요한 버퍼 zeroing 제거(1.2초, -1.6%)
    • 작은 숫자 구조체의 PartialEq 구현을 빠른 바이트 비교로 변경(0.5초, -0.7%)
  • 신규 unsafe 코드 없이 각 최적화가 수십 줄 이내로 간결
  • 유지관리자 협업과 PR 검토를 거쳐 신뢰도와 품질 개선을 동시에 달성함
  • 아직 약 6%의 성능 격차가 남아 있어, 추가 profiler-기반 비교 최적화 연구 여지가 충분함

Go ahead and give this a try! Maybe rav1d can eventually become faster than dav1d 👀🦀.

Read Entire Article