Rav1d 视频解码器通过优化临时缓冲区和数据结构性能提升 1%
近期,memorysafety.org 宣布举办一项竞赛,旨在改进 rav1d 视频解码器的性能。作为 rav1d 的开发者之一,我决定尝试一下这项挑战,尽管可能无法正式参与竞赛。rav1d 是 dav1d 解码器的 Rust 版本移植,通过 c2rust 工具从 dav1d 的 C 代码转换而来,并加入了 dav1d 的优化汇编函数,同时进行了 Rust 风格和安全性改良。目前 rav1d 比 dav1d 约慢 5%,我的目标是在不引入新的不安全代码的情况下,提高 rav1d 的性能。 为了对比 rav1d 和 dav1d 的性能,我使用了超线程技术(Hyperfine)和基准测试文件,在同一台 MacBook Air M3 上运行。测试结果显示,rav1d 在单线程模式下比 dav1d 慢约 9%(6 秒)。为了进一步优化,我使用了采样分析工具 samply 进行性能分析。 首先,我发现 rav1d 中有一个缓冲区初始化的问题。在 dav1d 中,cdef_filter_neon 函数中使用的 tmp_buf 缓冲区并未被显式初始化,而 rav1d 的 cdef_filter_neon_erased 函数却在每次调用时都会对 tmp_buf 进行零初始化。通过使用 MaybeUninit 类型,我们可以避免这种不必要的初始化操作,从而提高性能。实施这一优化后,cdef_filter_neon_erased 函数的自样本数从 670 降到了 274,整体性能提升了约 1.5%(1.2 秒)。 其次,我注意到一个名为 add_temporal_candidate 的函数在 Rust 版本中表现明显不如 C 版本。这个函数主要包含一些简单的条件判断和循环,但它的某些比较操作却显得非常耗时。具体来说,Mv 结构体的字段比较操作在默认实现中生成的代码效率较低。通过重新定义 Mv 结构体的 PartialEq 实现,使用字节级比较而非字段级比较,可以显著优化这部分代码的性能。实施这一优化后,add_temporal_candidate 函数的性能得到了改善,整体运行时间再次缩短了 0.5 秒,达到了 2.3% 的总提升。 经过这两项优化,rav1d 的运行时间从原来的 73.914 秒提高到了 72.644 秒,与 dav1d 的差距缩小到了大约 6%。尽管仍有优化空间,但这些改进已经取得了显著的效果。 业内评价: 这次优化展示了即使在复杂的视频解码器项目中,通过细致的性能分析和针对性的优化,也能取得明显的效果。尤其是避免不必要的内存初始化和重新定义小结构体的比较方法,这些方法虽然简单,但在高性能计算中却显得尤为重要。rav1d 项目的维护者也非常支持这次优化,并帮助改进了提交的代码,使其更加安全和高效。未来还有更多潜在的优化点值得探索,也许有一天 rav1d 能够超越 dav1d。如果你对这个话题感兴趣,可以参考这篇讨论帖,并阅读相关的其他技术文章。