rav1d 비디오 디코더 성능 개선
(ohadravid.github.io)- 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 👀🦀.
Hacker News 의견
- u16 두 개를 비교하는 이슈가 흥미로운 주제라는 의견 공유, 관련 이슈 링크 제공 https://github.com/rust-lang/rust/issues/140167
- store forwarding이 논의에서 언급되지 않은 점이 의외라는 놀람 표현, -O3에서의 코드 생성 결과는 지나치지만 -O2에서는 합리적 판단, 구조체 중 하나가 연산 직후라면 32비트 로드 시도 시 store forwarding 실패로 성능 향상이 무의미해질 수 있다는 구체적 설명, 비인라인/비-PGO 상황에서는 컴파일러가 최적화 적합성 판단에 필요한 정보 부족함 지적
- 이슈 토론이 단순한 “나도 겪었다” “언제 고쳐지나” 류의 댓글로 채워지지 않아 좋다는 감상, 웹 개발자로서 GitHub 이슈는 불만족이라는 솔직한 의견 공유
- 이 사례가 컴파일러 개발이 얼마나 복잡한 일인지 보여주는 예시라는 의견, C 계열 컴파일러들도 이런 이슈를 더 잘 다루지 못할 것이라는 확신 표현
- 블로그 포스트에 프로파일러 결과를 어떻게 삽입했는지 궁금증 표출, HTML 노드를 그대로 복사한 것인지 질문
- 버퍼 초기화(제로잉) 생략의 성능 이점에 관한 글이 며칠 전 관련 글 이후에 올라온 것이 흥미롭다는 소감, 과거 글 링크로 공유 https://news.ycombinator.com/item?id=44032680
- 본문 제목이 실제 성과에 비해 너무 소극적이라는 지적, 실제로는 두 가지 좋은 최적화 덕분에 2.3% 속도 향상이 있음 강조
- 1.5% 개선은 aarch64에만 해당되므로 전체 수치로 언급하는 것은 다소 공정하지 않다는 의견, arm/x86 비중을 고려하면 절반 정도로 보는 게 맞다는 주장
- 게시글이 유익했고 16비트 정수 쌍 비교의 비효율적 코드 발견이 인상적이었다는 평가
- Rust/LLVM 개발자들이 이 최적화를 가능한 경우 자동으로 적용할 수 있을지 궁금함, Rust에서는 메모리 초기화 관련 정보가 훨씬 정확하다는 점 언급
- 모든 조건이 같다면 이런 코덱은 Rust보다는 WUFFS 같은 언어나 그에 준하는 특수 목적 언어에서 다뤄야 한다고 생각, dav1d처럼 복잡한 코드를 WUFFS로 변환하는 것은 기존 C 코드 번역(clean up)보다 훨씬 더 큰 난이도라는 체감 공유, 그럼에도 이런 시도를 가치 있다고 여기며 문명적 차원에서 투자할 만하다는 주장
- WUFFS는 Matroska, webm, mp4와 같은 컨테이너 파싱에는 적합하지만 비디오 디코더에는 전혀 적합하지 않다는 점 설명, 다이나믹 메모리 할당이 없어 동적 데이터 처리에 도전적임, 비디오 코덱은 단순히 파일을 파싱하는 것이 아니라 매우 다양한 동적 상태 관리 필요성 강조
- rav1d 현상금 진행 상황이 궁금해서 혼잣말 삼아 묻고 있었는데, 비슷한 고민을 가진 사람이 있다는 공감 표현
- 재미있는 밈으로 시작하는 글은 좋은 포스팅임을 알 수 있다는 소감, 최근 화제였던 “Rav1d AV1 Decoder Rust 최적화 $2만 현상금” 논의와 연관성 언급, 관련 링크 추가 https://news.ycombinator.com/item?id=43982238
- 이 사례는 명확한 ‘Nominative determinism(이름짓기 효과)’ 케이스라는 위트 섞인 의견
- 솔직히 첫 번째 최적화는 perf만 잘 써도 쉽게 찾을 수 있는 흔한 유형이므로 다소 의외였음, 제로잉 이슈는 이미 첫 포스트에서 논의된 줄 알았음, 두 번째 최적화는 더 복잡하고 흥미로웠지만 역시 perf가 이끈 방향이었다는 점 강조, perf 툴의 효용성 과소평가 말라는 조언
- perf만 사용한 것이 아니라 C 버전과 Rust 버전 간의 차등 프로파일링 및 수동 매칭 과정을 통해 파악한 점 정확히 설명, perf diff 기능이 있지만 심볼명이 달라서 자동 매칭이 어렵다는 한계점 지적
- aarch64 기반 Apple 기기 관점에서 접근했다는 점 언급, 서로 다른 배경을 가진 사람이면 뒷날 되돌아보면 "당연했던" 부분도 빠르게 포착할 수 있음을 경험에서 강조
- 최근 ffmpeg 트위터 계정이 Rust 관련 이슈로 입장 표명을 하게 된 배경에 이번 이슈가 있다는 추측, 해당 트윗 링크 공유 https://x.com/ffmpeg/status/1924137645988356437?s=46
- ffmpeg 트위터 계정을 읽고 ffmpeg 사용에 회의감이 들었다는 솔직한 심경, 대안이 없어서 아쉽다는 아쉬움, 개발자 커뮤니티가 매우 독소적이라는 지적, 최대 성능이 중요할 수 있지만 외부와 데이터를 주고받는 환경에선 ffmpeg에서 매년 수차례 원격 취약점(CVE) 발생 가능성 언급, 보안 측면에서 타이트한 샌드박스 필요성 강조, 빠르고 안전한 솔루션을 함께 만들어가는 중간 지점이 필요하다는 의견, 관련 링크 공유 https://ffmpeg.org/security.html
- 더 나은 반응은 dav1d 성능 개선으로 대응하는 방법이라는 제안, 스포츠 기록과 비교해 지나치게 기록만 개선해봐야 실제 신기록 달성에 비하면 감흥이 떨어진다는 비유, 진짜 해결책은 실질적으로 빠르고 혁신적인 결과라는 유쾌한 설명