优化

3. 《游戏性能优化之路》测试&发现问题

  • 3.1 发现问题 vs 定位问题
    • 3.1.1 避免迷失在数据森林
    • 3.1.2 什么是问题
    • 3.1.3 什么是好的监控
    • 3.1.4 和测试人员与测试开发成为最好的朋友
  • 3.2 找到趁手的工具
    • 3.2.1 我需要什么工具
    • 3.2.2 CPU & 卡顿
    • 3.2.3 内存
    • 3.2.4 GPU
  • 3.3 搭建监控平台
    • 3.3.1 自动化平台的重要性
    • 3.3.2 高频率/高覆盖率
    • 3.3.3 高可信度
    • 3.3.4 易扩展
    • 3.3.5 自动筛选异常数据
  • 3.4 生产问题是一个大问题
    • 3.4.1 资产问题
    • 3.4.2 前验和后验流程
    • 3.4.3单元测试
    • 3.4.4 布设问题
    • 3.4.5 静态和动态热力图

3.1 发现问题VS定位问题

前面我们已经知道如何制定目标以及何时制定目标,现在我们需要直面真实的大部分性能优化工作者的工作,发现问题和定位问题。

3.1.1 避免迷失在数据森林

我们这里需要指明一个大量性能优化者都会混淆的事情,发现问题不等于定位问题。

发现问题的关键在于高效准确的监控,我们能够在问题出现的第一时间发现某一个维度的数据出现了问题,让我们快速找到可能需要出警的方向。

定位问题必须在明确在哪个维度出现了问题之后再进行,否则我们会花大量的时间在无意义的profile上,相同的数据看上上百上千遍,这是毫无意义的。

本章我们会聚焦前者,我们如何搭建高效的监控体系,作为生产环节当中的几乎是最下游,性能优化必须将问题暴露的时机不断往上游推,在更上游源头的时候就解决问题,真正留到下游的问题不能太多,否则我们不得不通过一些暴力方式强制让性能达成目标,对于项目必然是弊大于利的。

当我们建立起自动化平台之后从某个时间点开始我们一定会遇到一个问题,报告看不过来了。我们都知道,活干不过来了,有两个方向:

  • 招人招人
  • 开发自动化

闭着眼睛选,我们应该也知道自动化将会是最优解。那么问题来了,如何自动化地筛选出可能会有问题的数据呢。只有人知道什么东西有问题,你才能让代码知道什么是有问题的。

3.1.2 什么是问题

要知道什么是有问题的数据,我们必须祭出我们的统计学工具。很显然我们不需要非常复杂的统计学基础,我们只要筛选出异常数据就行,更不需要机器学习深度学习(就像几年前我以前的老板尝试让我搞得一样)。

最大最小值、均值、中位数、四分位,已经涵盖了大部分我们需要关心的问题了。对比指标有两个维度

  • 一个是我们的budget,我们的目标到底是什么,我们距离目标还有多远。
  • 一个是我们有没有显著劣化,有没有新的问题出现

前者很显然并不是我们日常发现问题的手段,而是目标,在真正上线前的很长一段时间达到这个数据是我们的目标,我们也不希望我们的平台上总是显示的全是红色。

后者是我们真正需要关心的问题,但是这里的问题是,仅仅和上一次测试周期比较真的是够的吗,这里有个很现实的问题:比如我的上一个测试周期出现了问题,导致了内存出现了异常,那么我这次性能周期测试出的数据,内存和上次没有变化,问题还在,但是如果仅和上次的对比有问题就变成了没问题。所以我们不应该和上周进行对比,而应该是历史数据进行对比。这里说一些题外话,投资领域有一个有意思的东西叫做定投,因为普通的金融小白并不知道什么是一个投资品的高点还是低点,当你进行定投之后,你的总量总是均值回归的,只要你不是在最高点的时候疯狂买入,你总是能够将你的买入成本保持在均值成本上。我们回来,看我们的均值数据,也是同样的道理,我们的数值有高有低,当我们的正常数据量足够大的时候,我们总是可以认为历史均值是可靠的。当然,在开发期的时候我们手上的异常数据可能非常多,这个时候我们需要手动的方式剔除一些异常数据,最终我们拿到我们所期望的均值数据,然后再利用前面提到的各个统计学指标告诉我们,什么是对的什么是错的,而不需要AI来处理,因为你要让AI知道什么是对的什么是错的也需要你的输入,garbage in garbage out,一股脑将所有的性能数据塞给AI,AI哪知道哪个是正常数据。

不要将AI神化,但是我们可以让AI做大量的数据整理工作,而非决策工作(一个数据是否有问题)。把决策权留给我们自己。

3.1.3 什么是好的监控

知道什么是问题之后,我们需要有性能平台能够承载我们的性能数据。

一个好的数据监控我觉得需要有以下特质:

  • 高频率
  • 高覆盖率
  • 高可信度
  • 自动筛选异常数据
  • 易扩展

前两个非常好理解,本质上性能数据监控就是性能方向的QA,高频率和高覆盖率决定了我们发现问题的概率,一个好的QA需要及时发现问题,要覆盖尽可能全的case来保证边缘问题不会被遗漏,将所有bug拖到封板再进行修复我认为并不是一个很好的习惯。日常解决的bug会逆向推动项目中不合理的开发迭代,流程问题、框架中的漏洞都会在平时暴露出来并且修复,如果在封板之后再去解决大部分问题,很多事情已经船大难掉头,“似乎有些问题,我们未及时应对,现在已经为时已晚”。性能也是一样,即使是开发期(当然不是demo期),也需要充分关注性能当中出现的异常情况,这样才能及时纠正错误的选型,被忽略的资产生产流程等。

高可信度同样是非常重要的点,我经常遇到这样的问题,专项QA同学给到的报告和之前的报告进行对比,发现两条曲线牛头不对马嘴,或者是莫名奇妙GPU耗时涨了一倍,最后发现是case阻塞了,或者是调整的屏幕分辨率但是没有知会到性能。一个有问题的报告危害性比一个报告出不来问题更大,所以如果你是性能相关的专项QA同学,我的建议是宁可多花些时间仔细检查,少跑几份报告,也不要让有问题的报告流到性能分析同学的手上,否则可能多花一个下午看一个无意义的报告还是小事,导致错误的决策,一个漏掉的性能bug成本都会比等待的时间大。

易扩展是一个优秀开发人员的素养,但是为了以防万一,我还是要在这里提一嘴,因为我就曾经遇到过这样的情况,发现一位测开同学将后验当中的每一个栏目都写成了单独的逻辑,当我发现的时候已经发现开发成本已经是一个新栏目需要1整天的开发量,因为我深知项目开发的后续流程当中后验是不断在扩充的,扩充得越多,浪费的人天也就越多,而且代码无法复用,这个后验支持一键开单,而其他的都不支持需要单独开发。另外一个反面是我们的数据采集工具,我在编写的时候特意提供了meta信息,每一个报告都包含自己的自描述信息,所以我只需要在客户端采集的地方写完监控的代码,平台上面自动就会显示所有相关我提供的信息,0扩展成本,一来一回,省了非常多的人天,而且增加监控也不需要再有什么复杂操作和提前量,一切都是符合直觉。

最后自动筛选异常数据则是用于解决先前提到的,报告太多,我们陷入到数据森林无法自拔,性能平台需要提供统计手段,筛选出有问题的数据,把性能同学的时间从发现问题中解放,不被蠢问题和老问题拖累,永远在对抗最新问题的第一线,专注定位问题,而不是每天做着同样的事情。

3.1.4 和测试人员与测试开发成为最好的朋友

从前面的内容我们可以明显了解到,性能优化中发现问题最重要的环节是自动化,这依赖测开同学开发的平台和测试同学编写的case以及维护工作。

在瑞达利欧的《原则》一书当中提到,将团队看作是一台日常运作的机器,如何将这台机器执行得更加高效,需要我们不断的维护,并且发现改进之处持续迭代。

自动化环节的效率取决于测试同学的日常流程和测开同学开发的平台的易用性,尽可能多地和QA和测开同学沟通,发现流程当中的问题,在一些测试环节的细节上,测试人员往往比性能开发更加专业,比如电流表拆机、设备自动化等工具也往往更依赖测开同学的能力。所以不要闷头自己想办法,多和测试测开同学交流,集思广益,可能会发现不一样角度的收获。你也要和他们尽可能多地提出测试环节中的要求,你所需要哪些数据、维度需要控制哪些变量,都需要事先约定并且落地为文档,尽可能避免平台和case制作环节中因为忽略了某些性能中关键的要素而大量试错。

3.2 找到趁手的工具

虽然我们在前面提到了大量的性能平台和监控相关的内容,但是我觉得在定位问题之前,我依旧认为需要知道有哪些工具可以分析问题。这不仅是因为工具也包含发现问题,而且当我们处于一个小团队,或者没有足够资源搭建性能监控平台的时候,性能工具可能是我们仅有可以依靠的东西。

分析性能的工具有很多,即使没有做过性能肯定也知道非常多的工具。但是很显然,我们知道不可能有工具能够分析一切类型的问题,我们需要针对我们想要分析的问题、问题发生的平台、定位的精确性、overhead高低等一系列条件来选择我们的工具。

首先我们来认识一下不同维度的性能下我们可以使用哪些工具来进行分析。因为工具很多,我并不会非常详细地介绍每一个工具的用法,但是我会附加每一个工具的文档或者官方网站,学习每一种工具的使用是非常重要的,需要十分了解每一个工具的优点和缺点,你知道这个工具能做什么你才能够选择工具。

3.2.1 CPU & 卡顿

Windows

Windows自带了一些性能分析工具,例如:WPR、WPA,他们基于ETW分析,详细文档可以看微软的官方文档。

https://learn.microsoft.com/zh-cn/windows-hardware/test/wpt

不过Windows自带的工具并不是很好用,同样用ETW,SuperLuminal是一个更好用的工具。

https://superluminal.eu

除了基于ETW的性能分析器,Windows可以使用VTune,用于微指令、IO、多线程等等瓶颈的分析。

https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler.html

通过PIX也可以用于录制CPU

https://devblogs.microsoft.com/pix

Android

Android本身是基于linux开发的,linux中的simpleperf也可以在Android上使用。

https://developer.android.com/ndk/guides/simpleperf?hl=zh-cn

Google本身也提供了用于分析cpu性能的工具:systrace/perfetto

针对较新的Android设备推荐使用perfetto

https://perfetto.dev/docs/tracing-101

perfetto提供了大量的采集项,包含基于采样的cpu分析、线程状态分析、系统调用分析等等。

IOS

IOS中大部分的工具都在Instument中,IOS提供了不同层级的profile工具,针对CPU逻辑,可以使用CPU Profiler,基于采样可以分析大致的堆栈耗时,CPUProfiler的overhead会相比Timer Profiler更低。

如果你需要分析微指令耗时,可以使用Processor Trace来进行分析,不过一般这用于分析具体算法,只有几秒就会生成几个G的文件。

https://developer.apple.com/documentation/xcode/analyzing-cpu-usage-with-processor-trace

XCode会告诉你是因为过多的cachemissing还是因为memory bound导致的。

Unity

Unity提供了Profiler作为CPU分析工具,本身基于打点,可以用于观察一帧当中的耗时分布如何

https://docs.unity3d.com/cn/2021.1/Manual/Profiler.html

除了耗时以外,Profiler还提供了大量的其他信息,比如UI的Batch、JobSystem的依赖关系、内存信息等等,如果不需要录制长时间数据的话确实还是比较好用的。

由于Profiler只能分析一帧的timeline,所以又提供了Profiler Analyzer用于统计各种统计学信息

https://docs.unity3d.com/Packages/com.unity.performance.profile-analyzer@0.4/manual/profiler-analyzer-window.html

不过我在实际项目开发过程当中会发现Profiler并无法满足长时间录制的需求,往往需要提供另外一套采集系统来支持长时间录制提高覆盖率。我直接实现了一个类似于Unreal的Stats的录制系统,并且在此基础上支持了GC的录制以及兼容Profiler打点,当然不同的项目解决这个问题的方式也五花八门。

Unreal

Unreal提供了两种主要的性能分析工具:Stats和UnrealInsight。

Stats只包含堆栈,不包含timeline信息,适合录制长时间信息。

https://dev.epicgames.com/documentation/zh-cn/unreal-engine/stat-commands-in-unreal-engine

而Insight则包含了Timeline信息,也支持资产加载等信息。

https://dev.epicgames.com/documentation/zh-cn/unreal-engine/unreal-insights-in-unreal-engine

第三方分析工具

Tracy:https://github.com/wolfpld/tracy

OpTicks:https://github.com/bombomby/optick

3.2.2 内存

Windows

上面提到的PIX也可以用于分析内存

文档:https://devblogs.microsoft.com/pix/memory-allocation-captures/

文档:https://devblogs.microsoft.com/pix/new-memory-profiling-features-in-timing-captures/

通过系统接口可以拿到对应的关键数据

https://learn.microsoft.com/en-us/windows/win32/memory/memory-management

Android

游戏大部分分配都来自于native,所以我们大部分情况下并不关心java堆的大小。

Android Studio可以用于分析内存分配

https://developer.android.com/studio/profile/record-native-allocations?hl=zh-cn

perfetto也可以分析内存,这里有更详细的内存分析文档

https://perfetto.dev/docs/case-studies/memory

IOS

IOS的大部分工具都远比Windows和Android好用,并且IOS本身的内存压力也是3个平台中最大的,所以我非常推荐从IOS开始分析你的内存。

GameMemory分析

https://developer.apple.com/documentation/xcode/analyzing-the-memory-usage-of-your-metal-app

IOS的内存可以很方便地通过Instrument分析,实时记录allocation log,对于泄漏分析也非常有用

https://www.youtube.com/watch?v=-7nofNyERYc

同时也可以通过memgraph来分析,通过malloc_history dump出大部分的内存

https://developer.apple.com/videos/play/wwdc2018/416

Unity

Unity可以通过Profiler工具观察内存概览,但是无法看到详细细节。

通过MemoryProfiler可以dump所有的逻辑对象和托管对象以及Unity中的Rootlabel和gfx对象,方便用于基于业务的内存分析:

https://docs.unity3d.com/Packages/com.unity.memoryprofiler@1.1/manual/index.html

也有第三方提供用于分析MemorySnapshot的工具

https://github.com/pschraut/UnityHeapExplorer

我十分建议对MemoryProfiler的数据进行自解析,以得到更多维度的数据分析。

Unreal

Unreal提供了两个工具用于内存分析:Memreport和MemoryInsight。

Memreport定位类似于Unity的Snapshot,但是由于完全基于文本,信息量对于实际分析来说远远不够,我强烈建议开发类似于unity snapshot的profiler工具因为本质上Unreal所有的Object基于UObject也支持通过Outer等依赖查询,本身底子比Unity更好,甚至能拿到更细的数据,但是因为缺乏相关工具的开发而只能用文本分析是非常不合理的。

https://www.unrealengine.com/en-US/blog/debugging-and-optimizing-memory

另外MemoryInsight类似于Memgraph,可以获取地址的实际分配位置,用于分析程序模块的内存分配效率。

https://dev.epicgames.com/documentation/en-us/unreal-engine/memory-insights-in-unreal-engine

https://developer.apple.com/documentation/xcode/optimizing-gpu-performance

3.2.3 GPU

Windows

RenderDoc支持Windows和Android的截帧分析

https://renderdoc.org

Intel GPA

https://www.intel.cn/content/www/cn/zh/docs/gpa/get-started-guide/2022-4/overview.html

nsight

https://developer.nvidia.com/nsight-systems

PIX也可以分析一部分GPU问题

Android

Renderdoc同样支持分析Android渲染

高通提供了snapdragon profiler

https://www.qualcomm.com/developer/software/snapdragon-profiler

Mali也提供了自己的Profiler

https://developer.arm.com/Tools and Software/Graphics Analyzer

IOS

通过GamePerformance可以了解大量Metal相关的信息,实际的渲染线程耗时、指令利用率

https://developer.apple.com/documentation/xcode/analyzing-the-performance-of-your-metal-app

通过FrameCapture可以了解到GPU用到了多少Resource,实际的RenderGraph是什么样的,每个pass花了多少时间等。

https://developer.apple.com/documentation/xcode/capturing-a-metal-workload-in-xcode

Unity

Unity提供了FrameDebugger用于分析每帧渲染过程

https://docs.unity3d.com/cn/2021.3/Manual/FrameDebugger.html

Unreal

Unreal有官方RenderDoc插件,所以你想要分析截帧也非常方便

https://dev.epicgames.com/documentation/en-us/unreal-engine/using-renderdoc-with-unreal-engine

3.2.4 我需要什么工具

以我个人的经验而言,性能分析工具分为两大类,和我们的这一章和下一章分别对应:发现问题、定位问题。

用于发现问题的工具往往有着更低的overhead、更长的录制时间覆,这也就意味着覆盖率会更高,让我们能尽可能避免漏掉某些特殊的case,但这也意味着需要牺牲精确性,往往我们只能看到问题大概的方向而无法定位实际真正的问题,基于打点或者基于系统概览数据收集的工具往往都拥有这个特点。

而用于定位问题的工具,一般有着更全的数据,例如详细的堆栈、地址,甚至可以提供每个指令消耗的时间,但这意味着更大的数据文件,只能录制更短的时间,这往往要求我们已经知道问题的方向在哪里,然后我们再通过复现的方式来录制,而更有针对性地看问题,而不是陷入在数据森林里面。例如:基于采样的cpu分析,或者是基于分配器hook的内存分析。

除了上述的分类方法以外,是基于业务还是基于程序模块也是很重要的一个点,当我要确定问题所在的时候,我必须知道这是因为程序本身的效率不行还是由于业务开发当中使用了不恰当的配置导致占用过多资产。例如你用instrument无法知道我创建了多少个entity,只能知道堆栈当中分配了多少内存,如果用UE的memreport或者Unity的MemoryProfiler则可以知道目前内存当中有多少逻辑对象,这是否合理或者符合项目制定的budget。

3.3 搭建监控平台

我们在上面介绍了很多工具,我们可以通过这些工具得到大量的信息,但是我们不能把我们的所有时间都放到Profile上,我们需要更多的自动化。我们回到监控平台的设计。

3.3.1 自动化平台的重要性

自动化的重要性不言而喻,但是自动化平台的搭建需要大量资源的支持,不同公司环境不同,对相关基建的支持程度也不同,必要性也不一样。

最好的情况是在项目组当中有专门的工具组或者测试开发团队,这样有利于对于自动化平台的快速迭代,也能根据具体项目需求快速进行迭代。还有一些公司会有一些自动化平台的中台,这种情况下自动化平台的迭代或许会受到一些限制,而且反馈相对而言会比较慢,平台的接口也不一定能完全符合项目的要求。而对于小团队而言,自动化平台则根据性能本身的优先级来确定需要投入的成本。

前面提到了监控平台的一些要点,我们详细来描述一下:

3.3.2 高频率/高覆盖率

高频率和高覆盖率的重要性,在之前我们已经提过,更高的频率和更高的覆盖率能让我们更快速发现问题,以及更少地遗漏问题。那我们如何做到这件事情呢?当然最简单的方式必然是增加机器以及维护的人力,但是这个成本对于大部分项目而言并不总是能够接受的。所以我们需要寻找另外的方法,我在设计自动化平台的时候其实发觉到其实想要高频率和高覆盖率地进行测试,首先测试本身的时间不能太长,假设一次八项热力图需要跑2个小时,那么每天一台设备能跑的测试数量数量上线也就是时间除以一次跑测所需的时间。当我们有更多地图的时候,就会遇到瓶颈。

有以下思路:

  1. 减少执行成本:减少非必要的运行时case,通过离线估计拟合运行时数据。
  2. 提高数据利用率:尽可能减少源头数据的数量,尽可能增加基于现有源头数据监控的维度。

第一个很好理解,比如我要跑运行时的热力图获取功耗、内存、Drawcall、面数等数据,会有登录、等待streaming稳定等等让case时间变长的变量,那么我是否可以通过单元测试计算每一个资产的基础指标,然后通过离线的方式布设的config拟合出运行时实际的开销。离线的case可以高频率执行,短时间内也就能覆盖更多的case。 第二个则是我们尽可能需要在同一次case当中拿到尽可能多的数据,比如我们有一部分采集指标的效率较低,会影响CPU,而另外一些则是用于测试CPU数据。那么我们在不影响数据准确性的情况下尽可能的通过尽量少的case获取这些我们所需的数据。另外我们会有基于时间的数据和基于点位的数据,我们不需要跑两次,我们只需要通过同时采集当前的时间和位置就能同时获取到两个维度的信息,只是通过不同的视图展示出来,通过这种方式我们可以展现的数据维度会大大提高,而数据采集量并不会有明显上涨。

当然,减少错误报告的数量也能让我们尽可能减少重新跑case的情况,这样也有助于减少我们需要拿到一次数据所需要的时间。

3.3.3 高可信度

高可信度指的是性能平台的数据要尽可能符合玩家平时跑测所能够感知到的数据,自动化平台作为游戏的外部环境,要尽可能地控制变量,并且降低自身的overhead。自动化平台的overhead来自于多个方面,有一些非常常见的我会在这里进行列举:

  1. 在运行时上传数据:这回显著增加运行时的开销,比较推荐的做法是在子线程异步写入到文件,并且在case结束之后再上传到网站
  2. 视频采集:通过内录的方式采集视频会对当前设备造成可观的性能开销,最建议的方式还是通过翻录方式采集视频,但是确实会带来不小的维护成本。
  3. 设备设置控制:分辨率、手机亮度、音量大小需要统一控制,并且在后续的测试当中不能轻易改变。前者会影响到渲染效率,而亮度和音量可能会影响到功耗。
  4. 温度控制、锁频:对于移动端测试的时候我们需要注意的是,我们的CPU测试是否要添加风扇,当我们需要环比CPU耗时的时候大部分情况下需要添加风扇,否则降频会导致cpu性能迅速下降导致无法环比。
  5. 性能模式:不少的android厂商为了平时的低功耗省电,会限制soc的频率,如果你的游戏没有被认出是游戏app频率就会被受到限制,得到错误的数据。可以手动将游戏添加到性能模式,或者和厂商沟通处理。
  6. 游戏自身bug导致的问题。很多时候case跑测出来错误的数据是因为游戏本身出现了bug,这个时候往往还需要QA同学跟进并且修复之后重新跑测。这个问题无法根治,但是可以通过在Case当中将可能多添加assert来告知测试这个报告存在问题,及时跟进,更不要把错误的报告给到性能同学。

稳定性始终是自动化平台需要解决的问题,但是自动化平台往往没有一蹴而就,逐步迭代,在不断发现问题当中迭代,可信度问题总是可以被克服的。

3.3.4 易扩展

易扩展是一切程序开发当中需要关注的事情,自动化平台也是如此。不同的自动化需求扩展性的方向也不同,但是总之和设计模式当中的原则一样,对扩展开放对修改关闭。

例如我同样要录制一条曲线报告,当我需要添加新的指标的时候我不希望平台开发同学需要参与其中,只需要我进行配置,或者是编写一个采集类变可以完全自动地在自动化平台上面展示出来。或者是我要添加一个新的单元测试、后验我只需要提供通用的数据格式,以及网站上的配置,测开同学不需要有新的修改。除非是新增的需求,例如:白名单、一键开单或者是其他单独的feature。对于某种类型的页面允许进行特化,但是所有的自动化类型需要拥有自己的baseline,所有的通用功能都会在此基础上进行迭代,而特化逻辑则通过类似于插件代码来进行支持。

只有这样测开同学才能从繁复的重复代码中解脱出来,而去做更有意义的事情,从而进一步提高自动化平台的易用性和功能。

同上述对高频率高覆盖率的描述类似,对于数据库设计我们会有比较少的源头数据,但是会cache各种各样维度的数据,例如同样是一个case,其中的数据可以通过时间域进行统计(曲线),也可以通过空间域进行统计(交互地图),也可以通过玩法或者地图进行统计(case分区块),只有多维度的设计才能做到前面所提到的提高数据利用率的可能。而且这也是高扩展性的考量点之一。

3.3.5 自动筛选异常数据

当我们拥有大量的自动化数据之后,我们肯定会遇到报告看不过来的问题。自动化平台本身还应担负筛选问题报告的职责。在前面的章节里面我们描述了我们如何在报告当中找到不合理的数据,我们需要尽可能将这些规则落地到实际代码当中,这样自动化平台会自动帮我们筛选出有问题的数据。还可以更进一步,例如在筛选出有问题数据之后自动触发录制定位问题的详细数据或者是一键对有问题数据进行开单(例如单元测试不通过的资产)。这会大大减少性能同学跟进重复问题的工作量,当然,做这一步的时候往往已经是项目比较靠后的时期,否则大概率会没有人力去跟进或者修改对应的资产,往这部分自动化开发的成本也可以通过具体需要来调整优先级,但我觉得有和没有这部分功能对性能同学的工作量而言会有天翻地覆的变化。

除了自动化筛选数据以外,还需要有更全面的全局概览来观察项目当中的问题分布,例如单元测试还剩下多少问题、后验还剩下多少问题、热力图布设当中还存在多少问题、具体case当中还有哪些问题。有了这一些数据,我们可以更顺利地推进对应模块的负责人修复问题,如果遭到拒绝,我们也可以通过这份数据说服更上层的领导来帮助我们解决问题。

别忘了,性能优化很大一部分工作就是通过数据说明你的问题是否值得去解决,要花多少成本解决。

3.4 生产问题又是一个大问题

在项目开发当中,我们遇到的性能问题按照分类的话有两种:

一种是程序问题,逻辑本身的低效导致了性能差距,这往往可以通过代码调整来解决。

而另一种是生产问题,美术制作了不合理复杂度的资产或者是策划进行了大量不合理的布设,这往往需要通过美术或者策划的工作量来进行修复,意味着人力的支出。

3.4.1 资产问题

在上线之前,我们可以快速通过屏蔽部分feature,或者用粗暴的方式对代码进行临时修复,但是资产就不一样了,需要投入大量的人力成本,如果用过于粗暴的方式美术效果会大打折扣,如果是对美术要求本身有要求的项目是不可能接受这种情况的。所以性能优化同学和TA、TD需要通力合作,通过流程、规范等手段尽可能在上游阶段就解决大量的性能隐患。

在此之前,我想说的是,性能并不是一个无限权力的职位,性能无法确定策划的布设设计,也无法决定美术质量,我们能够确定的只有我们能够接受的性能底线。所以想要推进资产问题,我们需要帮手,这里的帮手就是TA和TD。

就和所有的项目推进一样,TA和TD凭什么要帮你推进这件事情,对于TA和TD而言他们需要对美术和设计负责,需要对工作流效率负责,除此之外,你需要向他们传递一个信息:你们还需要对性能负责,这需要更上层的负责人向其关注这一信息,并且让其确认自己所在的角色。性能本身需要成为TA和TD(并不是所有人,而是必须有对应的接口人)来对最终的性能表现负责,性能优化人需要告诉他们我们的源头指标是什么,功耗、内存、耗时,每一块蛋糕要怎么区分需要TA和TD来做决定。游戏的调性需要更强调角色、玩法、场景还是其他什么别的东西,以此作为依据为资产制作定大致的方向,最终通过量化的方式落实到资产制作管线当中。

下面我会提到生产管线当中常常会遇到的一些平台/工具。从最上游到下游分别是:前后验、单元测试、热力图、玩法case测试。

3.4.2 前验后验流程

对于资产而言,通过前验是解决问题成本最低的手段,本质上前验和用IDE写代码时候的代码报错是相同的,能够在生产者在制作的过程当中就暴露出来,当即就可以马上把问题修复,都不会提交到仓库中,比较多的方式是在资产浏览器当中添加报错窗口或者是标红,并且在提交器当中添加检查hook脚本,大部分的版本管理工具都会支持hook。

但是前验并不是符合所有的情况,比如检查的耗时比较长的时候可能会大量消耗制作者本地的CPU和内存资源。这个时候就该让后验流程出马了,定期通过管线离线检查所有的资产,检查资产本身的合规性,例如面数、贴图大小、Collider合规性,命名合法性等。其中命名合法性以及目录标准化是非常关键的,如果没有规范化也就没有rule存在的空间。所以一旦发现命名问题或者目录标准问题需要第一时间进行修复,如果还有未管控的资产,也需要第一时间进行支持,这是优先级最高的事项没有之一,在管线制作当中第一个就要考虑的就是这个问题。

通过前验和后验,我们可以解决80%源资产非运行时当中的问题。

3.4.3 单元测试

解决了离线资产的问题之后,我们还需要通过单元测试来获得资产运行时的开销,例如角色、特效、物件、场景的Drawcall、面数、内存、功耗、加载速度。在刚启用单元测试的时候我们需要尽可能早地进行摸底,得到当前资产的平均消耗,从而了解在游戏当中我们可以看到多少的角色、特效、场景并且由此来确定后续布设问题当中具体的标准,从而知道LA、LD在场景当中进行布设。如果估计出来的结果本身并不满足美术和策划对密度的要求,那么或许我们需要考虑某些优化或者是美术资产本身标准的修改,例如NPC通过动画贴图实现或者LOD距离调整等等。当然这完全是理想情况下的流程,现实当中往往各个管线会进行穿插,并不能立刻确定资产的规格,最佳的方式还是在确定一个大概的标准值之后,在不断的开发实践当中调整,直到得到符合我们预期的指标。日常中源头指标更重要,比如内存和功耗有明显超标的情况下必须需要修复,而drawcall和面数则更多作为一个参考指标,允许一定超标但不允许出现大幅度超标的情况。

通过定期单元测试,我们能够明确了解到美术资产制作当中的性能达标情况,并且及时进行修复。

3.4.4 布设/配置问题

布设和配置本身的性能建立在资产本身是否合规的基础之上,但是我们在项目开发当中,资产制作和布设往往会穿插进行,我们无法保证在布设或者配置的时候资产已经完全ready。所以我们在进行布设和配置的检查时候需要对其组成部分进行抽象,例如一个NPC使用大概多少的功耗、内存等。这样我们即使在没有拿到最终资产本身的情况下也能对布设和配置进行一个初步的评估。

当然不同的游戏类型的布设和配置方式并不一样,对于MMO、大世界游戏,往往会需要对具体地图位置的性能进行评估,热力图是一种比较好的方式来表现评估地图性能的手段。

3.4.5 静态动态热力图

热力图本身可以容纳各种各样的数据。我们可以通过运行时采集热力图数据来得到实际每个点位的数据,我们在这里把运行时采集数据的热力图称作动态热力图,因为所有的数据都运行时数据,更接近于运行时的实际数据,但是每次测试的结果大概率会不一样,主要用于关键节点性能的验收。但是如果我们需要获取所有点位的数据会消耗大量的时间,LA和LD也无法及时看到当前的布设是否合理。

在前面的章节里面我们提到了一些增加性能测试频率的方法,我们需要将部分运行时才能获得到的数据转换为离线能够模拟的数据,我们可以通过单元测试得到的单个资产的运行时性能进行统计,大致获知某个资产在运行时实际消耗的资源,而后通过场景配置、Entity配置等数据来源推算出大概的分布,其中我们可能还需要估计距离带来的LOD以及遮挡带来的渲染差异,忽略这些情况会导致我们获得的数据偏高。具体的解决的方法,不同的项目并不相同,往往需要一些探索来逐步迭代出适合项目的统计方式。

通过静态数据的方式我们能够采集出各个点位静态模拟的数据,通过这些数据我们可以初步估计运行时的开销,并且在我们真正能在地图跑图能够运行之前发现有问题的布设,和单元测试一样,静态布设的标准我们不需要卡得太死,因为各种各样的原因,我们通过静态热力图得到的值会出现偏差,我们会处理那些有明显问题的布设,而如果本身超标并不严重(例如在20%以内)那我们在最后验收动态热力图的时候可以再处理这些问题也不迟,甚至你在处理动态热力图的时候发现当前的点甚至没有超标。

关于选点,初步观察地图布设,可以通过均匀撒点的方式进行八项热力图观察,并且以消耗最高的点为最终数据,静态热力图也比较偏向于均匀撒点的方式来处理。均匀撒点的问题是,和实际玩家跑测的情况有比较大的出入,并且很难覆盖y轴的情况。当我们能够开始跑图的时候,可以通过跑图case的形式来展示到热力图之上,覆盖一些关键的流程。也可以让美术选取一些他们自己比较关心的点位来观测其性能情况。不同的项目选点策略也会不同,主要基建本身足够完备,选点策略本身是很灵活的。

总结

对于测试和发现问题来说,监控平台和检查管线是非常重要的手段。在制作这些管线之后我们可以通过实际的profile工具和系统接口了解我们能够拿到哪些数据用于监控,以及确定哪些数据是存在问题的。对于监控平台和检查管线本身,我们会有一些比较明确的重点需要关注,在实现的过程当中要尽可能满足这些要求,并且根据项目的不同需要尽可能快地进行迭代和调整。随着监控和检查管线的成熟,测试和发现问题的成本和大幅下降,我们有更多的精力可以放在定位问题上。

下一章,我们会探讨我们如何定位问题所在的地方,是资产布设、客户端逻辑还是数据结构和算法导致微指令问题引发的性能问题。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注