std::vector 最佳实践 — 长期反复使用的 vector 内存稳定策略
0. 背景
生产上碰到程序内存持续上升,且无异常,表现为观察 VmRSS 不断增加。经多轮分析调研后,定位为 std::vector 的容量问题。特提出最佳实践,予以记录。
1. 核心规则
长期反复使用的 vector,必须在创建完后立即 reserve(合理大小),保证内存相对稳定。
2. 根因
std::vector::clear() 只清空元素,不释放 capacity:
std::vector<int> vec; |
capacity 的行为特征:
| 操作 | 对 capacity 的影响 |
|---|---|
push_back 超出 capacity |
扩容 (约 2x) |
clear() |
不变 |
erase() |
不变 |
resize(n) 当 n < capacity |
不变 |
reserve(n) 当 n > capacity |
扩至 n |
reserve(n) 当 n ≤ capacity |
不变 |
shrink_to_fit() |
缩至 size (非强制, 实现定义) |
swap 技巧 |
缩至 size |
结论:capacity 只升不降(shrink_to_fit 除外),是内存高水位的直接原因。
3. 反复 clear + push_back 的问题
// 典型 WorkAction 参数更新周期 |
每次更新元素数量波动:
更新1: add 500 → capacity 扩至 512 |
capacity 取历史最大值,永不回落。
4. 解决方案:创建后立即 reserve
4.1 基本模式
std::vector<SysTime> list; |
4.2 reserve 大小选择
| 策略 | reserve 值 | 优点 | 风险 |
|---|---|---|---|
| 取业务峰值 | 历史最大元素数 | 完全消除扩容 | 峰值过大时浪费 |
| 取峰值 × 1.2 | 留 20% 余量 | 覆盖突发增长 | 略多占用 |
| 取平均 × 2 | 覆盖多数场景 | 平衡 | 偶尔仍需扩容 |
4.3 实验验证
std::vector Node2K (1992B) 测试:
| 模式 | 轮次 | RSS 变化 | drift |
|---|---|---|---|
| reserve(1000) + clear+add | 10000 | 3,484 → 7,372 KB (稳定) | 0 KB |
| 不 reserve + clear+add | 10000 | capacity 增至 2048, RSS 不回落 | 持续增长 |
| 100实例 × reserve(1000) | 1000 | RSS 稳定在 392,548 KB | 0 KB |
固定 capacity 后,10000 轮迭代 RSS 完全稳定,drift = 0。
5. 配合策略
元素数不可预估时:clear + shrink_to_fit + reserve
while (running) { |
适用于:每次更新前可预估元素数量的场景。
6. 适用范围
此规则适用于所有基于 std::vector 的封装:
7. 规则总结
- 创建 vector 后立即 reserve(合理大小)
- reserve 时机: 对象构造后、首次使用前
- reserve 大小: 取业务峰值或略高, 宁可多 20% 不可少
- 清空后若需释放: clear() + shrink_to_fit()
- 不可预估时: clear() + shrink_to_fit() + reserve(预估值)
- 绝对禁止: 反复 clear + push_back 而不 reserve
注意:
shrink_to_fit 可能会造成内存碎片,但不会导致 RSS growth。
8. 测试数据
| 测试 | 模式 | 10000轮后 RSS | drift |
|---|---|---|---|
| A | 固定 capacity, clear+add | 5,424 KB (固定) | 0 KB |
| B | 固定 capacity, clear+shrink+add | 7,416 KB (固定) | 0 KB |
| C | 不固定 capacity (对照) | 7,496 KB | capacity 增至 2048 |
| D | 100实例 × 固定 capacity × 1000轮 | 198,012 KB (固定) | 0 KB |