前言
网上有很多无关内存的优良文章(比方《Unity 游戏内存散布概览》),看完后收益颇多,总感觉对内存(比方 PSS 的散布)曾经一目了然。直到最近遇到游戏中播放奥义导致 GfxDriver 内存暴涨 500MB 左右的问题,才发现之前的“一目了然”到真正解决问题之间,还有一段路要走。这段路,就是实践到实际过程中的方法论,而这方法论,或多或少是有迹可寻的。因而借这个机会,尝试去总结一下,同时分享给大家,欢送探讨。
“分享一次查找 GfxDriver 内存暴涨的经验”这个题目,其实是取自 UWA 上的一篇分享。正所谓“幸福”(GfxDriver 内存暴涨)是相似的,但各有各的“可怜”(暴涨起因不尽相同)。好了,废话不多说,让咱们进入正题。
问题形容
某个角色进入战斗后,只有开释奥义,PSS 霎时暴涨靠近 500MB,如下图所示:PSS 间接从 1228MB 涨到 1724.19MB,并且霎时达到峰值后又会回落一部分,直到维持在一个高位。
从上图中咱们能够看出两个问题:
- PSS 霎时暴涨
- PSS 达到峰值后又会回落一部分
问题定位
1. 初步定位
1.1 放大范畴
通过以上的问题形容,首先通过简略的测试放大问题范畴:
1)是否跟设施兼容性无关
2)是否跟后效无关
3)PSS 暴涨的大头局部在哪里
对于第 1 条和第 2 条,自测或请 QA 帮忙可能很快定位:这是个通用问题且跟后效无关;对于第 3 条,我的办法是应用以下三个工具进行组合判断:
1)GamePerf(或者 UWA、PerfDog、UPR 等都能够)
2)ADB shell dumpsys meminfo
3)Unity Profiler
上面具体讲解一下:
- 从 GamePerf 报告剖析初步断定是显存局部增长过快,如下图所示:
- 与此同时,应用 dumpsys meminfo 查看播放奥义前后两次 PSS 的快照,这样可能大体定位问题所在:
上图是奥义播放前的快照,下图是奥义播放后的快照,通过比照发现涨幅都集中在 GL mtrack。
- 再联合真机在 Unity Profiler 上的后果,显示 GfxDriver 从 127MB 涨到 0.56GB:
1.2 小结
通过以上三个工具组合,咱们能够大抵定位 PSS 的增长大头在“显存”上。但咱们晓得,手机上是没有独显的,SoC 中 GPU 和 CPU 共用一块 LPDDR 物理内存,因而我在显存上加上了引号。而以上三个工具别离引出了无关“显存”的三个概念,前面咱们会深刻理解一下以下三个概念:
GamePerf——memGraphics
PSS——GL mtrack
Unity Profiler——GfxDriver
2. 单元测试
既然已定位到,那么接下来就能够通过单元测试来进一步定位问题所在了。在这个阶段,就要引入新的工具——System Profiler。在测试之前,先简略介绍一下这个工具。
2.1 工具抉择——System Profiler
System Profiler 是华为提供给开发者的一款用于 Android 平台应用程序的性能数据实时采样工具。通过性能数据的实时动态变化与利用的动静场景相结合做关联剖析,帮忙开发者疾速定位应用程序的性能问题。它能够采集的数据有:
- CPU 性能数据指标:CPU 负载、CPU 各核使用率、CPU 各核频率和 CPU 性能计数器。
- GPU 性能数据指标:GPU 频率、GPU 负载和 GPU 性能计数器。
- Memory 性能数据指标:零碎 Memory 应用状况、利用 APP 过程 Memory 应用状况和 GPU Memory 应用状况。
- Graphics 性能数据指标:帧耗时 FrameTime、实时帧率 FPS、卡顿 Jank 和重大卡顿 Big jank。
- 其余性能数据指标:设施 CPU 温度、GPU 温度、电池温度、网络数据流量速率、Disk 数据读写速率和用户自定义性能数据事件。
为什么会选用这个工具呢,次要是从以下几个方面思考的:
- 通过以上的初步定位,内存暴涨问题跟机型无关
-
须要看到 PSS 的实时变动
- dumpsys meminfo 无奈满足实时这个需要
- Unity Profiler 的数据又比拟局限,无奈纵观全局
- Android Profiler 尽管可能看到 PSS 的实时变动,但跟 dumpsys 相比,Android Profiler 没有 System 和 Private Other 项,然而多了一个 Others 项,须要通过一些换算能力跟 dumpsys 进去的 PSS 匹配
- 最好可能看到除了 PSS 之外其它的一些性能指标,不便对问题做进一步排查定位
2.2 单个特效播放测试
2.2.1 测试数据
2.2.2 剖析
- System Profiler 中 Graphics 的含意跟 APP Summary 中的 Graphics 一样,播放前后的差距为 16.7MB
当初只粗略算一下特效的其中一个 Shader,一个顶点占用为:4(4+3+2+4+4)= 68Byte,数量为:101681,由此算出占用大小大略为:68 101681 / 1024 / 1024 = 6.59MB。
struct VertexInput
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float2 texcoord0 : TEXCOORD0;
float4 texcoord1 : TEXCOORD1;
float4 vertexColor : COLOR;
};
2.3 12 个特效霎时播放测试
先阐明一下,通过后期的状况摸底,游戏中奥义的播放会霎时播放 12 个雷同特效。因而,单元测试还须要模仿测试一下游戏中的真实情况,看看霎时播放 12 个雷同特效的话成果如何。
2.3.1 测试数据
2.3.2 剖析
-
NativeHeap 相差 13MB(135.6-122.9)左右,Native 示意从 C 或 C ++ 代码调配对象的内存(因为 Unity 的底层是 C ++ 写的,大部分对象的创立都是在 C ++ 实现的,这部分内存就会进入 Native 中,而 C# 那边就是一个对 C ++ 援用和操作的“壳”,这部分会进入到堆内存即 Mono 中,而 Mono 内存则在 Unknown 中体现),次要由以下几局部组成:
- Mesh
- Font
- Fmod
- Texture(R/W)
- Material/Shader
- Animation Clip 等
- Graphics 仍然是大头所在,随着 12 个特效霎时播放,Graphics 从 59.7MB 霎时增长到了 251.5MB。再联合上图中顶点数从 5445 暴涨到 1180293,根本能够断定,造成 PSS 霎时增长 200MB 的起因是顶点数量的暴涨。同时揣测游戏中 PSS 暴涨 500MB 也是这个逻辑:如下图所示,游戏中奥义播放后每帧顶点数峰值为 212 万,相比单元测试中的 115 万,数量正好是 2 倍左右,再加上游戏中小奥义播放时还有其它特效在同时播放,根本就会达到 500MB 左右了。
- 为什么 Graphics 达到峰值后会回落一部分直到维持在一个高位呢?
对于这个问题,我这边也翻阅了大量材料,尽可能地解释一下,预计还是会有不对的中央,欢送大家来探讨!
首先明确几个概念:
- CPU 的内存个别称之为主存(Main Memory),GPU 本人的存储则称为 Local Memory,即 GPU 的本地存储,有时候也称为 Video Memory(即咱们通常所说的显存)
- 手机 SoC 上 GPU 没有本人的物理存储设备,而是共享 CPU 的存储空间,即 Unified Memory Architecture(一致性存储架构),通常是从 CPU 的存储中划分一部分进去作为该 GPU 的 Local Memory
其次,咱们要理解 CPU 和 GPU 在渲染时数据传输的工作原理:
CPU 将顶点数据放入主存当中,供 GPU 应用。因为主存的内容对 GPU 来说是不可见的,所以 GPU 是不能间接拜访这些数据的。为了让 GPU 拜访 CPU 主存的内容,业界引入了一个叫 GART(即 Graphic Address Remapping Table)的技术。GART 是一个内存地址的映射表,能够将 CPU 的内存地址从新映射到 GPU 的地址空间,这样就能够让 GPU 间接拜访(DMA,Direct Memory Access)Host System Memory。
Pinned Memory 就是 CPU 内存上的一块专门用于 GART 的存储区域。
以 OpenGL 为例,当 CPU 须要更新数据给 GPU 应用时,比方顶点数据的更新、纹理数据的上传等,能够通过这两个函数:glBufferData 和 glBufferSubData,将数据从 Main Memory 拷贝到 Pinned Memory,一旦拷贝实现,就会发动一次异步的 DMA 传输,将数据传输给 GPU,而后就会从函数调用返回,一旦函数返回,就能够对原来 CPU 主存中的数据做任何解决——批改或者删除。
所以,这里大胆猜想一下,Graphics 达到峰值后迅速降落的局部应该是 Main Memory。
3. 精准定位
终于来到了最初一步:查出顶点暴涨的假相。
下图是模仿同时播放 12 个雷同特效的截帧,能够看到,光是 baozha_01 这么一个特效结点,它的顶点数就有 120660。把该特效中的 baozha_01 节点用到的模型拉进去看,也就只有 2011 个顶点,乘上 12 的话也只有 24132,这差的 10w 左右的顶点去哪了?
持续找起因,这次要通过 RederDoc 来截帧看看,这 120660 个顶点来自哪里。
看到这 5 个排列参差的球体,霎时明确了什么,连忙到粒子设置的中央看一下:
改成 2 个试试:
所以 120660 就是这么来的:120660 = 2011 x 5 x 12
至此,水落石出。
了解“显存”的三个概念
- GamePerf 的 memGraphics,它的官网文档上写着让咱们参考 Unity 的文章,外面没有 memGraphics 的概念,都是对内存的相干阐明,不过很值得一看。联合数据,这里大胆猜想 memGraphics 就对应着 APP Summary 中的 Graphics:指渲染相干的所有内存之和,包含 Gfx dev、EGL mtrack 和 GL mtrack 中所有 Private 局部之和。
- PSS 的 GL mtrack,其实是次要看 Gfxdev 和 GL mtrack,这里为什么只提到 GL mtrack 呢?大家看下面我的 PSS 截图,外面的确没有 Gfxdev,这是截自华为手机的,猜想是华为手机底层把 Gfxdev 和 GL mtrack 都统计成了 GL mtrack 了。因为如果换成高通 SoC,就会呈现 Gfxdev。下面 Unity 对 PSS 的介绍文章中也是把 Gfxdev 和 GL mtrack 放在一起阐明,这里间接翻译一下:GL 和 Gfx 是驱动反馈的 GPU 内存,次要是 GL 纹理大小的总和、GL 命令缓冲区、固定的全局驱动 RAM 耗费以及 Shader。须要指出,这些不会呈现在旧的 Android 版本上。留神:用户空间驱动和内核空间驱动共享同一个内存空间。在某些 Android 版本上,这个局部会被反复计算两次,因而 Gfxdev 要比实际上应用的数值更大。
- Unity 的 GfxDriver 其实统计的就是 Textures 和 Buffer(Vertex Buffer 以及 Index Buffer)的内存,Unity 源码是通过 REGISTER_EXTERNAL_GFX_ALLOCATION_REF 这个宏进行统计的,手头有源码的小伙伴能够去看一下。然而,Unity 官网文档上对 GfxDriver 的解释是:“The estimated amount of memory the driver uses on Textures, render targets, Shaders, and Mesh data”。对于多出的 Render Targets,笔者这边示意存疑,前面须要验证一下。
总结
最初,简略总结一下“方法论”!
所谓大道至简,先初步定位放大问题范畴,其次单元测试剖析问题所在,最初精准定位找到问题起因。我认为每个阶段中最重要的就是抉择趁手的工具。
- 初步定位——GamePerf(UWA、PerfDog、UPR 都能够)、Dumpsys Meminfo、Unity Profiler
- 单元测试——System Profiler
-
精准定位——Renderdoc
参考
[1] https://learn.unity.com/tutor…
[2] https://developer.nvidia.com/…
[3] https://developer.huawei.com/…
[4] https://www.cnblogs.com/hello…
[5] https://blog.csdn.net/msf5688…
[6] Unity 游戏内存散布概览
这是侑虎科技第 1217 篇文章,感激作者吕强供稿。欢送转发分享,未经作者受权请勿转载。如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ 群:793972859)
作者在 UWA 学堂公布的《五天实现 PBR 保姆级教程》课程,旨在对 PBR(Physically Based Rendering,基于物理的渲染)技术进行深入浅出地解说与实现。
再次感激吕强的分享,如果您有任何独到的见解或者发现也欢送分割咱们,一起探讨。(QQ 群:793972859)