ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

什么是 PSS?从游戏开发角度理解内存占用与共享成本

什么是 PSS?从游戏开发角度理解内存占用与共享成本 游戏运行一段时间后内存监控显示RSS1.8 GB PSS1.5 GB这两个数字为什么不一样判断游戏是否存在内存问题到底应该看哪个对于 Linux / Android 游戏开发PSS 是评估进程物理内存成本的重要指标。但如果把它理解成“游戏申请的内存”或“退出游戏后能释放的内存”就容易得出错误结论。本文从游戏资源、共享内存、性能分析和实际排查出发解释 PSS 的意义与使用方法。一、PSS 是什么PSS全称 Proportional Set Size通常译为“比例分摊驻留集”。它表示进程独占的驻留内存加上共享驻留内存按比例分摊后的份额。这里有两个关键词。1. 驻留统计的是当前驻留在物理内存 RAM 中的页面。只是保留了地址、尚未实际驻留的空间不会因为地址范围很大就全部计入 PSS。2. 比例分摊如果某个物理页面被多个进程共享PSS 不会把整页都算在每个进程头上而是分摊计算。直观公式是PSS 独占驻留内存 各共享驻留页面的分摊份额严格来说Linux 按页面的共享映射情况进行记账。用“共享进程数”理解常见场景很方便但它不是所有特殊映射情况的完整定义。二、为什么有了 RSS还需要 PSSRSS 表示进程当前映射了多少驻留在 RAM 中的页面其中包含共享页面。问题是同一份共享内存可能完整出现在多个进程的 RSS 中。假设游戏进程和一个辅助进程的内存如下内存部分游戏进程辅助进程独占驻留内存900 MB100 MB两者共享的驻留内存200 MB同一份 200 MB为了简化假设共享部分只有这两个进程映射。按 RSS 计算游戏 RSS 900 200 1100 MB 辅助进程 RSS 100 200 300 MB RSS 合计 1400 MB但实际物理内存只有900 100 200 1200 MB按 PSS 计算游戏 PSS 900 200 ÷ 2 1000 MB 辅助进程 PSS 100 200 ÷ 2 200 MB PSS 合计 1200 MB在这个简化例子里PSS 避免了共享内存的重复计算。因此RSS适合观察进程的驻留页面规模PSS更适合比较考虑共享后的物理内存成本。但不能把所有进程的 PSS 加起来就认为得到了整台机器的全部 RAM 使用量内核内存、某些设备分配等不一定包含在普通进程页面统计中。三、游戏中有哪些内存可能被计入 PSS典型游戏进程包含游戏代码与动态库 托管堆或脚本运行时 原生堆 线程栈 资源文件映射 共享内存 部分图形相关映射1. 游戏对象和运行时数据例如角色状态AI 数据场景节点物理对象动画运行时数据。这些数据通常位于进程的私有内存中。驻留后其成本通常主要由游戏自身承担。2. 动态库和系统组件系统库的只读代码页可能被多个进程共享因此在 PSS 中按比例记账。不过加载了同名库不代表库中的所有页面都能共享。可写数据、发生写时复制的页面以及不同来源的文件映射都可能具有不同的共享情况。3. 文件映射资源游戏使用mmap映射资源文件时映射整个文件 ≠ 整个文件立即进入 RAM ≠ PSS 立即增加同样大小通常只有实际驻留的映射页面才计入相关统计。文件大小、虚拟映射大小和当前 PSS是三个不同概念。4. 图形资源这里需要特别谨慎。纹理上传缓冲、CPU 侧资源副本、共享图形缓冲等可能影响进程或平台的内存统计。但普通 Linux PSS 不能视为完整的 GPU 内存账本。Android 的图形内存归属与展示还受到系统版本、驱动和统计接口影响分析时需要结合 GPU / Graphics 相关工具。四、为什么 PSS 不等于引擎 Profiler 中的内存在 Unity、Unreal 或自研引擎中经常出现引擎统计900 MB 系统 PSS1300 MB这不一定说明某个工具算错了。两者的统计对象可能根本不同。指标主要描述引擎已分配内存引擎追踪到的分配分配器保留内存内存池保留、尚未归还的空间系统 PSS按操作系统页面规则分摊的驻留成本GPU 资源统计由图形 API、引擎或驱动记录的资源成本例如引擎可能没有完整追踪第三方 SDK 的原生分配部分运行时和系统组件线程栈某些文件映射驱动相关分配。反过来引擎记录的某些已分配空间也可能尚未全部驻留或者已经被换出。正确做法是解释两者差异而不是强行让数字相等。五、PSS 曲线应该怎么解读场景一切场景时突然升高旧场景仍然存活 新资源读入 解压与解析缓冲 GPU 上传暂存 ↓ PSS 出现峰值这不一定是泄漏更可能是资源生命周期重叠。排查重点是能否提前释放旧资源是否同时加载了过多资源是否保存了不必要的中间副本临时缓冲何时真正释放或复用。场景二退出场景后PSS 没有立即下降也不一定是泄漏。对象释放之后内存可能仍留在托管堆原生分配器对象池引擎缓存。因此对象不再使用 ≠ 分配器立即归还页面 ≠ PSS 立即下降应同时观察“存活对象”“已分配量”和“分配器保留量”。场景三重复进入同一场景基线持续上涨例如第一次退出后800 MB 第二次退出后950 MB 第三次退出后1100 MB如果每次测试条件一致等待清理后仍持续增长就值得怀疑对象引用未解除原生资源未释放缓存无限增长资源重复加载第三方 SDK 泄漏。但仍需要通过对象快照、分配记录或映射变化找到证据不能只凭 PSS 定罪。场景四没有新分配PSS 也上涨了假设游戏与另一个进程共享 100 MB 页面两者共享时游戏分摊约 50 MB 另一个进程解除映射后游戏承担约 100 MB游戏没有新增这 50 MB 物理页面但 PSS 变大了。这说明PSS 既受自身行为影响也受共享关系变化影响。六、如何在 Android / Linux 上查看 PSSAndroid使用dumpsys meminfoadb shell dumpsys meminfo com.example.game通常可以看到 PSS 总量及 Native Heap、Dalvik、代码映射等分类具体格式随系统版本变化。如果游戏包含多个进程需要确认每个进程的统计范围不能只看主进程就认为覆盖了整个应用。Linux读取smaps_rollupcat/proc/pid/smaps_rollup在支持该接口且权限允许时可以看到Rss: Pss: Private_Clean: Private_Dirty: Swap: SwapPss:需要分析具体映射时再查看cat/proc/pid/smaps其中SwapPss是交换出去的页面按比例分摊后的统计不应与普通驻留 PSS 混为一谈。不要每帧采集完整 PSSPSS 统计需要检查页面映射信息可能比读取简单 RSS 计数更昂贵。推荐高频、低成本指标 → 观察整体趋势 关键节点 PSS 快照 → 分析驻留成本 分配与资源跟踪 → 定位峰值和来源完整 PSS 采样太频繁可能干扰测试采样太稀疏又可能漏掉短时峰值。七、PSS 高会导致游戏被系统杀掉吗PSS 高通常意味着游戏承担了较多物理内存成本但不存在跨设备、跨系统通用的“PSS 必杀线”。Android 的低内存终止还受到以下因素影响整机内存压力应用前后台状态与优先级系统及厂商策略内存回收效果适用的资源限制。此外Java 堆分配失败、原生分配失败、图形资源创建失败也不等于 LMKD 直接终止进程。所以PSS 是风险和成本指标不是单独决定游戏生死的开关。排查退出时还应检查系统日志和应用退出原因。八、游戏项目中怎样正确使用 PSS建议建立三层观测第一层系统成本记录PSS / RSS系统可用内存与压力交换或压缩内存相关指标图形内存相关统计。第二层引擎归属记录纹理Mesh动画音频托管堆原生分配器资源缓存。第三层行为时间线标记开始切场景 开始解压 完成资源创建 提交 GPU 上传 释放旧场景 完成回收把三层数据放在同一条时间线上才能回答“PSS 为什么在这里增加了 300 MB是哪些资源造成的这部分是必要常驻还是可以消除的峰值”总结从游戏开发角度看PSS 最重要的价值是在考虑共享内存之后衡量游戏进程大约应承担多少驻留物理内存成本。记住四点PSS 统计的是驻留成本不是虚拟地址大小。共享页面按比例分摊避免像 RSS 那样直接重复计算。PSS 上涨不必然是泄漏下降也不必然代表资源管理正确。结合引擎资源统计、分配记录和系统日志PSS 才能真正指导优化。PSS 告诉你“成本有多大”而资源与分配分析告诉你“钱花在了哪里”。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进