Cải thiện hiệu năng của bộ giải mã video rav1d
(ohadravid.github.io)- Bộ giải mã AV1 rav1d được viết bằng Rust được phát hiện chậm hơn khoảng 9% so với dav1d nền tảng C
- Xác nhận rằng tối ưu khởi tạo bộ đệm và cải thiện logic so sánh struct lần lượt mang lại mức tăng tốc 1,5% và 0,7%
- Sử dụng công cụ profiling samply để xác định cụ thể nguyên nhân chênh lệch hiệu năng giữa hai phiên bản
- Tăng hiệu quả bằng cách dùng phương thức so sánh theo byte thay cho triển khai mặc định của PartialEq trong Rust
- Với lần tối ưu này, đã cải thiện được khoảng 30% tổng chênh lệch hiệu năng, nhưng vẫn còn dư địa để tối ưu thêm
Bối cảnh và cách tiếp cận
- rav1d là dự án chuyển bộ giải mã AV1 dav1d sang Rust bằng c2rust, đồng thời phản ánh các hàm tối ưu asm và các cải tiến an toàn đặc trưng của ngôn ngữ Rust
- Một chuẩn hiệu năng cơ bản đã được công khai, và rav1d dựa trên Rust hiện đang chậm hơn khoảng 5% so với dav1d dựa trên C
- Thay vì xem xét toàn bộ cấu trúc của một bộ giải mã video phức tạp, việc phân tích tập trung vào chênh lệch thời gian chạy nhị phân với cùng một đầu vào
- So sánh một cách có hệ thống bằng công cụ đo hiệu năng (hyperfine) và profiler (samply)
- Môi trường thử nghiệm là chip macOS M3, được đơn giản hóa bằng cách chạy đơn luồng
Đo hiệu năng: so sánh giá trị mặc định
- Tiến hành build và benchmark với cùng một tệp thử nghiệm (Chimera-AV1-8bit-1920x1080-6736kbps.ivf)
- rav1d: khoảng 73,9 giây, dav1d: khoảng 67,9 giây, xác nhận chênh lệch thời gian chạy khoảng 6 giây (9%)
- Mỗi trình biên dịch (Clang, Rustc) đều sử dụng gần như cùng một phiên bản LLVM
Phân tích profiling
- Dùng profiler samply để so sánh số lượng mẫu theo từng hàm của mỗi tệp thực thi
- Tập trung kiểm tra đường gọi và phân bố mẫu của các hàm asm dựa trên NEON (ARM SIMD)
- dav1d tách ra thành các hàm filter riêng để gọi nhánh tới các hàm asm, còn rav1d quản lý tất cả bằng một hàm dispatch duy nhất
- Hàm
cdef_filter_neon_erasedcho thấy số mẫu Self nhiều hơn khoảng 270 so với tổng của hai hàm bên dav1d (tương ứng 1% toàn bộ) - Kết quả phân tích đã bắt được đoạn đang khởi tạo quá lớn một bộ đệm tạm (zero-initialized buffer) một cách không cần thiết
Tối ưu loại bỏ khởi tạo bộ đệm
- Vì lý do an toàn, Rust tự động thực hiện zeroing với cách như [0u16; LEN]
- Nhưng C (dav1d) không zeroing bộ đệm một cách tường minh, mà chỉ ghi giá trị vào vùng thực sự được sử dụng
- Trong Rust, dùng std::mem::MaybeUninit để loại bỏ chi phí khởi tạo không cần thiết
- Số mẫu Self của hàm
cdef_filter_neon_erasedgiảm mạnh từ 670 xuống còn 274 - Một bộ đệm Align16 dung lượng lớn khác cũng được hoist phần khởi tạo ra ngoài vòng lặp để giảm chi phí khởi tạo xuống còn 1 lần
- Sau tối ưu, benchmark còn khoảng 72,6 giây, cải thiện 1,2 giây (1,5%)
Tối ưu so sánh struct
- Phân tích inverted stack trong profiling phát hiện hàm add_temporal_candidate hoạt động kém hiệu quả hơn dự kiến
- Phần so sánh các trường của struct Mv trong hàm này (triển khai tự động của
PartialEq) sinh ra mã chậm một cách không cần thiết - Trong C,
unionđược dùng để thực hiện so sánh hiệu quả theo đơn vịuint32_t - Trong Rust, tránh dùng unsafe nhưng triển khai so sánh theo lát byte bằng trait zerocopy::AsBytes
- Tối ưu này tiếp tục mang lại mức tăng hiệu năng 0,5 giây (khoảng 0,7%)
Kết quả và tổng kết
- Hai tối ưu đơn giản (loại bỏ khởi tạo bộ đệm, so sánh struct theo byte) giúp rút ngắn thời gian chạy hơn 2%
- Vẫn còn khoảng 6% chênh lệch hiệu năng, và dư địa tối ưu thêm vẫn còn lớn
- Xác nhận rằng cách so sánh giữa các snapshot profiler là hiệu quả
- Khả năng tối ưu thêm dựa trên phân tích snapshot của rav1d và dav1d là rất cao
- Nhờ phản hồi tích cực và sự phối hợp của các maintainer dự án, có thể cải thiện mà không làm tổn hại đến tính an toàn
Tóm tắt
- Dùng công cụ profiler (samply) và benchmark (hyperfine) để phân tích chính xác chênh lệch thời gian chạy 6 giây (9%) giữa rav1d và dav1d
- Hai tối ưu chính:
- Loại bỏ zeroing bộ đệm không cần thiết trong mã tối ưu riêng cho ARM (1,2 giây, -1,6%)
- Đổi triển khai
PartialEqcủa một struct số nhỏ sang so sánh byte nhanh hơn (0,5 giây, -0,7%)
- Mỗi tối ưu đều gọn trong vài chục dòng và không cần thêm mã unsafe mới
- Thông qua cộng tác với maintainer và quá trình review PR, vừa nâng được độ tin cậy vừa cải thiện chất lượng
- Vẫn còn khoảng cách hiệu năng khoảng 6%, nên vẫn còn nhiều dư địa cho nghiên cứu tối ưu bổ sung dựa trên profiler
Hãy thử xem! Có lẽ rav1d cuối cùng sẽ còn nhanh hơn cả dav1d 👀🦀.
1 bình luận
Ý kiến Hacker News
u16là một chủ đề thú vị, đồng thời cung cấp liên kết tới issue liên quan https://github.com/rust-lang/rust/issues/140167store forwardingkhông được nhắc đến trong thảo luận; kết quả sinh mã ở-O3là hơi quá mức nhưng ở-O2thì là phán đoán hợp lý; giải thích cụ thể rằng nếu một trong các struct vừa mới được thao tác xong thì việc thử tải 32-bit có thể làmstore forwardingthất bại, khiến cải thiện hiệu năng trở nên vô nghĩa; chỉ ra rằng trong bối cảnh không inline/không PGO, compiler thiếu thông tin cần thiết để đánh giá mức độ phù hợp của tối ưu hóaaarch64, nên nhắc đến như một con số tổng thể thì hơi thiếu công bằng; cho rằng nếu xét tỷ trọngarm/x86thì nên tính khoảng một nửadav1dsang WUFFS khó hơn rất nhiều so với việc dịch/dọn dẹp mã C hiện có; dù vậy vẫn cho rằng nỗ lực như vậy rất đáng giá và xứng đáng được đầu tư ở tầm văn minhMatroska,webm,mp4, nhưng hoàn toàn không phù hợp cho video decoder; do không có cấp phát bộ nhớ động nên rất khó xử lý dữ liệu động; nhấn mạnh rằng video codec không chỉ đơn thuần là parse file mà còn cần quản lý một trạng thái động cực kỳ đa dạngrav1d, đồng thời bày tỏ sự đồng cảm khi thấy có người khác cũng có cùng băn khoănNominative determinism(hiệu ứng tên gọi)perftốt là có thể tìm ra dễ dàng; cũng cho rằng vấn đề zeroing tưởng như đã được bàn ở bài đầu tiên rồi; nhấn mạnh rằng tối ưu hóa thứ hai phức tạp và thú vị hơn, nhưng vẫn là hướng đi doperfdẫn ra; khuyên đừng đánh giá thấp độ hữu ích của công cụperfperf, mà còn xác định vấn đề thông qua quá trình profiling chênh lệch giữa bản C và bản Rust cùng với việc đối chiếu thủ công; chỉ ra hạn chế rằng dùperfcó tính năng diff, việc tự động ghép cặp là khó vì tên symbol khác nhauaarch64; nhấn mạnh từ kinh nghiệm rằng người có nền tảng khác nhau đôi khi có thể nhanh chóng nhận ra những điều mà sau này nhìn lại sẽ thấy là “quá hiển nhiên”dav1d; ví việc chỉ cải thiện chỉ số giống như thể thao chỉ chăm chăm chỉnh thành tích nhưng vẫn chưa bằng việc thực sự lập kỷ lục mới nên cảm giác không đã; diễn đạt vui rằng lời giải thực sự là kết quả thực chất, nhanh hơn và có tính đột phá hơn