作者参加了 2024 年的 Advent of Code 活动,主要用 Rust 语言解决其中的编程难题。
- 算法与问题描述:问题描述了一个由数据块组成的虚拟存储磁盘,每个块要么为空,要么包含文件数据,文件有基于其在磁盘上出现顺序分配的 ID。目标是对磁盘进行基本的碎片整理操作,将文件打包在一起以最大化连续空闲空间,最后计算磁盘的校验和作为问题的解决方案。
- 初始朴素解决方案:作者最初为尽快在排行榜上获得高分而快速编写了解决方案,使用嵌套循环和数组操作,在 7950x CPU 上运行需 139 毫秒,主要步骤为解析输入并构建相关变量,然后进行文件移动和校验和计算。
- 基于树的解决方案:为提高代码性能,作者改为直接跟踪磁盘中填充和空闲空间的跨度,避免构建整个磁盘布局,使用自定义的混合树数据结构来支持高效插入,但这种结构可能并非最优,运行时间降至 25.2 毫秒,提高了约 81.9%。
- 开始跨度索引计数:在调试基于树的解决方案时,作者意识到文件移动的特性,通过添加数组来跟踪每个文件大小的最小有效起始索引,使运行时间大幅减少至 224 微秒,提高了约 99.1%。
- 扁平化跨度解决方案:为避免基于树的解决方案中的间接性,作者将跨度以“扁平”方式存储,使用
smallvec
crate 存储文件,处理文件删除时避免动态添加/删除跨度,运行时间减少超过 50%,降至 85.3 微秒。 - 跨度 SoA(Struct of Arrays):将数据表示从数组结构切换为结构数组,虽结果导致性能略有下降,但作者决定坚持该优化,运行时间约为 92.3 微秒。
- MiniVec:作者注意到
SmallVec
检查导致访问内部数据效率低下,创建了自定义的MiniVec
结构,将跨度内联存储,避免边界检查,性能提升 25%,运行时间降至 70 微秒。 - 常量时间校验和:优化校验和计算,利用查找表将校验和计算变为常量时间,性能提升 7.6%,运行时间降至 64.7 微秒。
- 更好的输入解析:优化输入解析部分,预分配向量并使用不安全代码初始化部分向量,性能提升 9.3%,运行时间降至 58.7 微秒。
- SIMD 输入解析:使用 Rust 的标准库中的便携式 SIMD 进行输入解析,一次可解析 32 个数字,通过
deinterleave
等操作处理数据,性能提升 36.3%,运行时间降至 37.4 微秒。 - SIMD 空闲空间扫描:尝试使用 SIMD 查找左最空闲空间,但性能略有下降,变为 39.1 微秒。
- 最大未移动源 ID 计数:跟踪第一个未移动扇区的索引,提前停止校验和计算,性能提升 12.6%,运行时间降至 34.17 微秒。
- 完成数字计数记账:跟踪文件移动的条件,提前退出外部循环,性能提升 3.7%,运行时间降至 32.92 微秒。
- 更快的 MiniVec 初始化:优化
MiniVec
的初始化,减少存储需求,使用 SIMD 加速初始化,性能提升 23.6%,运行时间降至 25.14 微秒。 - 优化的 MiniVec::pop_front():通过将文件 ID 设为特定值来模拟删除文件,优化
pop_front()
操作,性能提升 1.8%,运行时间降至 24.7 微秒。 - 微调:进行一些微调,如使用更小的向量大小进行空闲空间查找,将数组访问改为
get_unchecked()
,重新排序控制流等,最终运行时间降至 23.28 微秒,比初始状态快 3 个数量级,大部分改进来自少数简单变化,但作者认为自己的解决方案可能并非最优。 - 总结与收获:Rust 的便携式 SIMD 很成熟且易用,良好的算法和数据结构比优化实现更重要,Advent of Code 是学习高性能 Rust 的好方式,现代 CPU 性能复杂且难以预测,需实际测试和基准测试来确定性能变化。作者还写了其他优化回顾帖子,可通过 RSS 或各种平台订阅获取后续内容。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。