前言

网上有很多无关内存的优良文章(比方《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,笔者这边示意存疑,前面须要验证一下。

总结

最初,简略总结一下“方法论”!

所谓大道至简,先初步定位放大问题范畴,其次单元测试剖析问题所在,最初精准定位找到问题起因。我认为每个阶段中最重要的就是抉择趁手的工具。

  1. 初步定位——GamePerf(UWA、PerfDog、UPR都能够)、Dumpsys Meminfo、Unity Profiler
  2. 单元测试——System Profiler
  3. 精准定位——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)