这是关于在多线程上优化 Rust 工作负载的第二篇文章。
优化内容总结:
- 内联(Inlining):添加
#[inline(always)]
使一些小函数内联,运行时减少约 30%,如定点算术的新类型函数。 - 编写更好的算术(Writing better arithmetic):乘法运算中,先使用
checked_mul()
判断乘积是否适合i64
,再决定是否使用i128
,运行时又减少 30%;优化BigInt
后端的乘法实现,直接使用BigInt
的除法,运行时减少七倍。 - 优化
BigInt
(OptimizingBigInt
):使用smallvec
优化BigInt
的内存布局,运行时减少 12% - 25%,多线程时相对增益更大。 - 面向数据的设计(Data-oriented design):通过使用
u8
索引、smallvec
技术、Box<[_]>
等优化选票数据结构,使运行时在最佳场景下减少 20%;尝试位打包虽减少数据缓存缺失,但使程序运行变慢 30%,且代码重构复杂。 - 排序和克隆数据(Sorting and cloning data):排序输入文件可减少分支缺失,使指令执行更高效,但会增加数据缓存缺失;克隆排序后的数据可使顺序读取更快,运行时减少 20%,但依赖分配器的隐式行为,生产中可使用自定义分配工具。
- 优化
BigRational
(OptimizingBigRational
):根据数学公式,将BigRational
的分母表示为质因数分解,进行部分 GCD/LCM 简化,最终全简化,使程序运行速度提高 14 - 18 倍,结合BigInt
的smallvec
优化,整体提高 15 - 20 倍。 通用优化(Generic optimizations):
- 日志过滤(Log filtering):使用
log
crate 的max_level_debug
功能和log_enabled!
宏,可减少运行时 5% - 10%,某些情况下可减少 40%。 - 禁用算术溢出检查(Disabling arithmetic overflow checks):在热循环中禁用算术溢出检查,运行时减少 20%。
- 恐慌 = 中止(Panic = abort):将
panic!
的行为改为中止程序,在使用大整数算术时运行时减少 15% - 20%,其他情况结果不一。 - codegen-units = 1:将每个 crate 的编译单元设置为 1,可使运行时在某些场景下提高 15%。
- LTO(Link-time optimization):启用 LTO 可使运行时在某些场景下提高 5%。
- PGO(Profile-guided optimization):尝试 PGO 未观察到改进,可能是测试时间短或 PGO 不是万能的。
- 目标本机 CPU(Targetting the native CPU):使用
target-cpu=native
选项未使运行时有明显变化。 - 切换分配器(Switching allocators):使用 jemalloc 未使运行时有明显变化,可能是程序的分配模式对默认分配器友好。
- 日志过滤(Log filtering):使用
- 内联(Inlining):添加
- 结果总结:最终选择的并行框架对性能不是最重要的,最大的改进来自算法(以更好的方式编写算术)、内存表示(
smallvec
、面向数据的设计)或通用优化(如强制函数内联)。更新的基准测试表明,单线程性能最佳,Rayon 在壁时间和用户时间上比自定义并行ism 慢 20%和两倍,使用 Rayon 时系统时间开销随线程数增加而显著增加,使用自定义并行ism 时系统时间开销稳定且最小。
总体而言,优化 Rust 程序需要综合考虑多个方面,以获得最佳性能。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。