Q57: 如何优化内存使用?
核心结论
内存优化不是单纯“省一点 RAM”,而是要同时控制:
- 总占用
- 分配频率
- 碎片
- 缓存局部性
真正有效的内存优化,往往不是最后去抠 allocator,而是先减少无意义对象、缩小热路径数据和控制生命周期。
一、先分清是哪一种内存问题
常见至少有四类:
- 内存泄漏
- 占用过大
- 分配过于频繁
- 布局导致缓存命中差
这四类问题的解法完全不同。
二、很多内存问题其实源于设计
例如:
- 对象模型过重
- 容器嵌套过深
- 大量重复字符串
- 把临时状态长期挂在对象上
如果设计层就过于膨胀,后面再做 allocator 优化收益往往有限。
三、先看热路径对象
在线游戏里最值得优化的,通常不是所有对象,而是这些高频对象:
- 实体状态
- 网络消息
- AOI 临时结构
- 定时器节点
- 路径点和战斗事件
这些对象如果过大、过碎、频繁分配,成本会被不断放大。
四、控制分配频率通常比抠总量更值
很多系统的卡顿和抖动来自:
- 高频
new/delete - 临时对象暴增
- 容器不断扩容
所以常见改法是:
- 预留容量
- 对象复用
- 批量分配
- 使用 arena 或对象池
这类优化往往直接影响尾延迟。
五、缓存局部性经常被低估
同样是 100MB 内存,占得多一点未必最致命;更大的问题常常是数据散得太碎,导致:
- CPU cache miss 增多
- 指针追逐严重
- 遍历性能差
所以内存优化不只是“少占”,还包括“摆得更合理”。
六、字符串和容器经常是隐形大头
常见问题包括:
- 大量重复字符串
unordered_map/std::map滥用- 小对象过多
- 容器里存太多指针和包装层
这类问题在大规模实体系统里会非常明显。
七、泄漏和峰值问题要分开治理
内存持续上涨和瞬时峰值过高,处理方式不同:
- 泄漏更关注生命周期闭环
- 峰值更关注瞬时分配和峰值路径
别把两者混在一起,否则排查会很慢。
八、工程上更稳妥的优化顺序
常见更有效的顺序是:
- 先测清楚大头对象和增长曲线
- 先砍无效对象和重复数据
- 再优化高频分配
- 再考虑自定义分配器和更激进策略
因为后者开发和维护成本更高。
九、常见误区
1. 内存优化就是上对象池
对象池有用,但它只解决一部分分配问题。
2. 内存没爆就不用管
很多问题会先以抖动、cache miss、GC 风格卡顿形式出现。
3. 把所有对象做小就是优化
如果可读性和边界被破坏,也可能得不偿失。关键是优化热路径。
参考资料
- 内存 profiling、arena 分配与数据导向布局相关资料
