Rust 内存优化实战:如何通过数学算法节省大量内存

Rust 内存优化实战:如何通过数学算法节省大量内存
在大规模数据系统中,内存往往比 CPU 更稀缺。我最近在处理一个日增 GB 级日志的分析服务时,发现把哈希表换成基于数学的布隆过滤器加基数排序方案后,内存直接降了 90%。核心思路很简单:用概率性数据结构代替精确存储,用数学关系代替哈希计算。
为什么哈希表吃内存?
我最初用的 HashMap<String, usize>,每个字符串都要分配堆内存,加上 HashMap 自带的哈希碰撞槽位,开销非常大。哪怕只是存一亿个整数,如果用 HashSet<i64>,每个元素至少占 24 字节(8字节数据 + 16字节哈希表节点),加起来就是近 2GB。

更糟的是,当数据量增长时,HashMap 会不断 rehash,内存峰值可能是最终大小的 1.5 倍。我在压测时见过内存瞬时飙到 8GB 的情况。
布隆过滤器:用比特位换空间
布隆过滤器的核心是用多个哈希函数映射到比特数组,判断元素「可能存在」或「一定不存在」。错误率可控,内存占用是字面级的。
use bloom_filter::BloomFilter;
// 创建能存 1000 万元素、错误率 1% 的布隆过滤器
let mut filter = BloomFilter::<u32>::new(10_000_000, 0.01).unwrap();
// 添加元素
for i in 0..10_000_000i32 {
filter.add(&i);
}
// 查询:返回 true 表示可能存在,false 表示一定不存在
assert!(filter.may_contain(&5000000));
assert!(!filter.may_contain(&99999999));
这个过滤器实际占用大约 10MB,而存同样多的 HashSet 要 240MB。对于只需要判断「是否存在」的场景,这是质的飞跃。
基数排序:省去存储,直接用数学关系
如果数据范围已知且连续,连布隆过滤器都嫌浪费。我遇到过一个场景:用户 ID 是 0 到 5000 万的整数,需要快速判断哪些 ID 存在。
直接用位图:5000 万个比特只有 6.25MB。用 bitvec crate 操作非常简洁:
use bitvec::bitvec;
let mut ids = bitvec![0; 50_000_000];
ids.set(12345, true);
ids.set(67890, true);
// 查询
assert_eq!(ids.get(12345).unwrap(), &true);
assert_eq!(ids.get(99999999).unwrap(), &false); // 越界也不 panic
如果数据范围更大(比如到 10 亿),可以用分块策略:把空间分成多个段,每段用位图,只加载需要的段到内存。这就是基数排序的思想——不用存数据本身,用位置关系表示数据。
混合方案:分层过滤降低误判
单一数据结构总有局限。我的实践是把布隆过滤器和位图组合:外层用布隆过滤器快速排除不可能存在的查询,内层到位图精确验证。
fn check_id(filter: &BloomFilter<u32>, bitmap: &BitVec, id: u32) -> bool {
if !filter.may_contain(&id) {
return false; // 布隆过滤器说没有,肯定没有
}
bitmap[id as usize] // 位图精确判断
}
这样既保留了布隆过滤器的低内存优势,又通过位图消除了误判。整个系统的内存占用从最初的 4GB 降到了 50MB 左右。
踩坑提醒
- 布隆过滤器的错误率要预留余量,我一开始按理论值设置,实际运行时发现哈希冲突比预期多
- 位图的随机访问虽然快,但位运算本身有开销,适合批量查询不适合单点高频查询
- Rust 的 ownership 系统在处理大位图时要小心,建议用
Arc<BitVec>共享读取
下一步
如果你正在处理大规模整数集合同样场景,可以考虑用 roaring bitmap 替代原生位图,它在稀疏数据下表现更好。也可以看看 T-trees 这种更高级的数据结构,在内存效率和查询性能之间找到了更好的平衡点。
本文首发于 Rust 内存优化实战:如何通过数学算法节省大量内存 — https://lyxq.com.cn/en/blog/rust-memory-optimization-math-algorithms
转载或引用请注明出处,商业使用请联系作者获得授权。