我们将讲解如何使用多个Unity工具,通过简单的方法收集性能指标并创建基准,这些工具包括:
- Unity Test Runner
- Unity Performance Testing Extension
- Unity Performance Benchmark Reporter
为什么在Unity中进行性能基准测试?
作为Unity的开发人员,通常会遇到以下情形:项目不久前还运行得流畅而快速,但进行一次或多次改动后,场景运行速度明显变慢,帧数也降了很多,其它性能问题开始不断出现。寻找造成性能下降的改动会非常困难。
如果你是一名得Unity合作伙伴,想要了解自己开发的SDK、驱动、平台、资源包或其它工具中的性能变化。或者想要收集自己产品在不同Unity版本的性能指标,但不知道如何进行收集和对比。
本文中示例所建立的性能基准可以带来很大帮助。现在我们将展示如何收集性能指标、通过这些指标创建基准并可视化查看性能指标的变化。
下载示例项目
本文将使用UnityPerformanceBenchmark示例性能测试项目中的测试代码。
请从GitHub下载最新版本的XRAutomatedTests,你可以在PerformanceTests子目录中找到UnityPerformanceBenchmark项目。
下载XRAutomatedTests:
https://github.com/Unity-Technologies/XRAutomatedTests/releases
在Unity Test Runner中编写性能测试
UnityPerformanceBenchmark项目包含多个示例场景,它们将通过Unity Performance Testing Extension用于Unity性能测试。
我们需要了解如何使用Unity Test Runner 和Unity Performance Testing Extension编写性能测试。在编写前性能测试之前,我们首先介绍这二个工具的信息。
1、Unity Test Runner
我们将使用Unity Test Runner运行性能测试。Unity Test Runner是内置在Unity编辑中的测试执行框架,它允许开发人员在编辑和运行模式下针对目标平台测试代码,例如:Standalone、Android系统或iOS系统。
2、Unity Performance Testing Extension
Unity Performance Testing Extension是一个Unity编辑器扩展包,它提供API和测试用例属性,允许开发人员在Unity编辑器和播放器中对Unity Profiler性能分析器标记和非性能分析器的自定义指标进行采样和聚合。
Unity Performance Test Extension要求使用Unity 2018.1或更高版本。如果在UnityPerformanceBenchmark项目中运行示例性能测试,或者使用Unity Performance Test Extension时,请确保使用Unity 2018.1或更高版本。
使用命令行打开示例项目
UnityPerformanceBenchmark项目实现了IPrebuildSetup接口,这是Unity Test Runner的组成功能,可以用来实现Setup方法,该方法会在Unity Test Runner执行测试运行前自动调用。
UnityPerformanceBenchmark项目的IPrebuildSetup.Setup方法首先会解析命令行参数,寻找播放器构建设置。该方法允许我们使用相同Unity项目灵活地为性能测试构建播放器,具体设置可以针对不同平台、渲染线程模式、播放器图形API、脚本实现以及立体渲染路径和VR SDK等支持XR的设置而定。
因此,我们需要从命令行用Unity打开UnityPerformanceBenchmark项目,传入在Unity Test Runner运行测试时要使用的播放器构建选项。
示例:在Windows系统启动UnityPerformanceBenchmark项目,构建Android播放器。
Unity.exe -projectPath C:\XRAutomatedTests-2018.2\PerformanceTests\UnityPerformanceBenchmark -testPlatform Android -buildTarget Android -playergraphicsapi=OpenGLES3 -mtRendering -scriptingbackend=mono |
指令会在Windows系统启动Unity,针对具有OpenGLES3图形API、多线程渲染功能和Mono脚本后端的Android系统进行构建。
示例:从OSX系统启动UnityPerformanceBenchmark项目以构建iOS播放器。
./Unity -projectPath /XRAutomatedTests-2018.2/PerformanceTests/UnityPerformanceBenchmark -testPlatform iOS -buildTarget iOS -playergraphicsapi=OpenGLES3 -mtRendering -scriptingbackend=mono -appleDeveloperTeamID=<yourAppleDeveloperTeamID> -iOSProvisioningProfileID=<yourIosProvisionProfileID> |
指令会在OSX系统启动Unity,并针对具有OpenGLES3图形API、多线程渲染功能和Mono脚本后端的iOS系统进行构建。该指令还提供了Apple开发团队信息和部署到iOS设备所需的配置文件信息。
当我们从命令行使用Unity打开UnityPerformanceBenchmark项目时,如以上示例所示,命令行参数将保存在内存中,以便IPrebuildSetup.Setup方法解析并用于构建播放器。
尽管在Unity Test Runner中运行测试时,不必使用从命令行启动的方法,但该方法可以避免为每个播放器配置使用单独的测试项目。
了解从命令行打开项目和运行测试时所用到的命令行选项,请参考《如何运行Unity性能基准测试》:
https://github.com/Unity-Technol … ance-Tests#howtorun
如果想要了如我们如何在测试项目中解析播放器构建设置,请查看UnityPerformanceBenchmark测试项目中,Scripts目录下的RenderPerformancePrebuildStep.cs文件。
打开Test Runner窗口
打开UnityPerformanceBenchmark项目后,我们需要在Unity编辑器中打开Unity Test Runner窗口。
1、在Unity 2018.1中,打开Window > Test Runner。
2、在Unity 2018.2中,打开Window > General > Test Runner。
Unity Test Runner窗口打开后,如下图所示。
截图中是我们的Unity性能测试,我们可以按下窗口左上角的“Run”按钮在Unity编辑器中运行测试,也可以针对实际设备或平台按下右上角的“Run all in player”按钮来运行测试。
如果想在IPrebuildSetup.Setup方法中调试代码,可通过以下步骤实现。
1、在Visual Studio中设置IPrebuildSetup.Setup代码的断点。
2、将Visual Studio Tool for Unity扩展附加到Unity编辑器。
3、按下Unity Test Runner窗口中的“Run All”或“Run Select”按钮,在编辑器运行测试。
此时,Visual Studio调试器会分析代码,你可以根据需要进行调试。
调试技巧
如果想在IPrebuildSetup.Setup方法中调试代码,可通过以下步骤实现。
1、在Visual Studio中设置IPrebuildSetup.Setup代码的断点。
2、将Visual Studio Tool for Unity扩展附加到Unity编辑器。
3、按下Unity Test Runner窗口中的“Run All”或“Run Select”按钮,在编辑器运行测试。
此时,Visual Studio调试器会分析代码,你可以根据需要进行调试。
Unity性能测试示例
下面让我们来查看一个性能测试的示例,更好地了解性能测试如何工作。
示例:在Unity性能测试中采样性能分析器标记
[PerformanceUnityTest] public IEnumerator SpiralFlame_RenderPerformance() { yield return SceneManager .LoadSceneAsync(spiralSceneName, LoadSceneMode.Additive); SetActiveScene(spiralSceneName); // 实例化场景中的性能测试对象 var renderPerformanceTest = SetupPerfTest<DynamicRenderPerformanceMonoBehaviourTest>(); // 使时间值在测量前稳定下来 yield return new WaitForSecondsRealtime(SettleTime); // 使用Performance Test Extension的ProfilerMarkers API using (Measure.ProfilerMarkers(SamplerNames)) { // 设置CaptureMetrics的标识为TRUE // 开始采集指标 renderPerformanceTest.component.CaptureMetrics = true ; // 运行MonoBehaviour Test yield return renderPerformanceTest; } yield return SceneManager.UnloadSceneAsync(spiralSceneName); } |
本示例中使用的测试方法是SpiralFlame_RenderPerformance。我们从方法装饰器[PerformanceUnityTest]可以知道,该方法是个Unity性能测试。
UnityPerformanceBenchmark测试项目中的所有测试内容都按照该测试方法中的相同模式:
1、加载场景进行测试;
2、将场景设置为活动状态,以便我们在测试方法中与其进行交互;
3、创建一个DynamicRenderPerformanceMonoBehaviourTest类型的测试对象,并将其添加到测试场景中。该过程发生在SetupPerfTest<T>方法中;
4、在开始采样指标前,并在加载和添加测试对象到场景后,等待场景的恒定时间值“稳定下来”;
5、通过Performance Test Extension API设置用于采集的性能分析标记;
6、告诉性能测试我们已准备好开始采集指标;
7、然后yield return测试对象即IMonoBehaviourTest,在渲染循环中采集指标。
我们也会在RenderPerformanceMonoBehaviourTestBase基类中不属于Unity性能分析器标记、帧计数或执行时间的自定义指标进行采样。
示例:在Monobehaviour脚本中采样自定义指标
private void Update() { if (CaptureMetrics) { FrameCount++; SampleFps(); #if ENABLE_VR if (XRSettings.enabled) { SampleGpuTimeLastFrame(); } #endif } if (IsMetricsCaptured) { EndMetricCapture(); } } |
private void SampleFps() { Fps = GetFps(); Measure.Custom(FpsSg, Fps); startFrameCount = Time.renderedFrameCount; } |
private void SampleGpuTimeLastFrame() { var gpuTimeLastFrame = GetGpuTimeLastFrame(); Measure.Custom(GpuTimeLastFrameSg, gpuTimeLastFrame * 1000); } |
public void EndMetricCapture() { CaptureMetrics = false ; #if UNITY_ANALYTICS && UNITY_2018_2_OR_NEWER Measure.Custom(startupTimeSg, appStartupTime); #endif } |
上面示例中,我们采集了FPS、GpuTimeLastFrame和应用程序启动时间。
1、IsTestFinished属性
在RenderPerformanceMonoBehaviourTestBase基类中,我们实现了属性public bool IsTestFinished。我们需要实现该属性,因为RenderPerformanceMonoBehaviourTestBase会实现IMonoBehaviourTest 接口。
该属性非常重要,因为Unity Test Runner使用它来了解何时停止测试。当它为true时,测试终止。开发者需要自己实现该逻辑以确定Unity Test Runner应该何时停止测试。
示例:采集IsTestFinished属性中的自定义指标
public bool IsTestFinished { get { bool isTestFinished = false ; if (IsMetricsCaptured) { Measure.Custom(objCountSg, RenderedGameObjects); Measure.Custom(trianglesSg, Tris); Measure.Custom(verticesSg, Verts); isTestFinished = true ; } return isTestFinished; } } |
本示例中,我们采集了测试完成时场景中已渲染游戏对象、三角形和顶点的数量信息。
2、SampleGroupDefinition
现在我们了解如何调用Performance Testing Extension来采样指标的示例,下面将介绍如何配置这些指标。
Measure.*方法通常接收SampleGroupDefinition结构作为参数。创建新SampleGroupDefinition时,要定义收集样本的属性。
示例:为GpuTimeLastFrame定义新SampleGroupDefinition,使用毫秒作为采样单位,使用最小值聚合样本
下面是用于GpuTimeLastFrame的SampleGroupDefinition代码。我们通过该代码让Performance Testing Extension知道如何收集样本并为GpuTimeLastFrame聚合。
该SampleGroupDefinition来自动态场景渲染性能测试示例,因此我们在此选择使用收集到的最小值聚合样本。为什么我们不使用例如:中位数或平均值,这样更常见的聚合度量呢?
这是因为场景是动态的。在动态场景中渲染过程会不断变化,所以使用中位数或平均值聚合会使运行相同代码的相同场景得到不可靠或不一致的结果。
如果我们想跟踪动态场景中渲染指标的单个聚合,这可能是最好的方法。但是,当我们为静态场景定义类似SampleGroupDefinition时,我们会使用中位数聚合。
new SampleGroupDefinition(GpuTimeLastFrameName, SampleUnit.Millisecond, AggregationType.Min) |
示例:为FPS定义新SampleGroupDefinition,将None用作样本单位,使用中位值来聚合样本,最好能提高数值
下面是为FPS使用的SampleGroupDefinition。FPS没有特定测量单位,FPS的单位就是FPS,所以在此指定SampleUnit.None。我们将使用中位数聚合类型。
该测试发生在静态场景中,所以我们不必担心不可预测的渲染体验。我们明确为样本组设定了15%的阈值,对increaseIsBetter参数传入了true,因为提高FPS是件好事。
当从命令行运行时,最后二个参数会被收集并保存在性能测试结果的.xml文件中,稍后可用于Unity Performance Benchmark Reporter,从而建立基准。
new SampleGroupDefinition(FpsName, SampleUnit.None, AggregationType.Median, threshold: 0.15, increaseIsBetter: true ) |
测试完成后,所有之前启用的指标样本都会由Performance Testing Extension进行聚合。
3、测量类型
在示例代码中,我们使用了几个不同的Unity Performance Testing Extension API,它们是:
1、Measure.ProfilerMarkers
2、Measure.Custom
Unity Performance Testing Extension还提供其它Measure方法,可以根据开发者在Unity中测试性能内容和方式来满足特定需求。其它方法包括:
1、Measure.Method
2、Measure.Frames
3、Measure.Scope
4、Measure.FrameTimes
关于不同Measure方法的更多信息,详请阅读:
https://docs.unity3d.com/Package … 1/manual/index.html
在Unity Test Runner中运行性能测试
现在我们已经展示了一些示例,了解如何使用Unity Test Runner和Unity Performance Testing Extension编写性能测试,接下来了解如何运行测试。
我们可以通过二个主要方式执行性能测试:
在命令行中使用-runTests 选项启动Unity
这是运行性能测试的首选方式,因为Unity Performance Test Extension会生成.xml文件,该文件可用于在Unity Performance Benchmark Reporter中查看并对比结果。
直接在Unity编辑器运行测试
使用条件是:
1、仅在Unity Test Runner运行测试并查看结果,不需要采集结果以供之后使用。
2、想要验证测试是否能够运行或者需要调试测试代码。
1、使用-runTests命令行选项运行性能测试
下面是如何使用Unity Test Runner从命令行运行性能测试的二个示例,。这些示例的代码似曾相识,因为我们在此使用了前面用过的示例,这些示例用来介绍如何从命令行打开UnityPerformanceBenchmark项目。
示例:在Windows系统针对Android播放器运行UnityPerformanceBenchmark性能测试
下面指令会在Windows系统启动Unity,针对具有OpenGLES3图形API、多线程渲染功能和Mono脚本后端的Android系统进行构建。
Unity.exe -runTests [-batchmode] -projectPath C:\XRAutomatedTests-2018.2\PerformanceTests\UnityPerformanceBenchmark -testPlatform Android -buildTarget Android -playergraphicsapi=OpenGLES3 -mtRendering -scriptingbackend=mono -testResults C:\PerfTests\results\PerfBenchmark_Android_OpenGLES3_MtRendering_Mono.xml -logfile C:\PerfTests\logs\PerfBenchmark_Android_OpenGLES3_MtRendering_Mono_UnityLog.txt |
示例:在OSX系统针对iOS播放器运行UnityPerformanceBenchmark性能测试
下面指令会在OSX系统启动Unity,并针对具有OpenGLES3图形API、多线程渲染功能和Mono脚本后端的iOS系统进行构建。该指令还提供了Apple开发团队信息和部署到iOS设备所需的配置文件信息。
. /Unity -runTests [-batchmode] -projectPath /XRAutomatedTests-2018 .2 /PerformanceTests/UnityPerformanceBenchmark -testPlatform iOS -buildTarget iOS -playergraphicsapi=OpenGLES3 -mtRendering -scriptingbackend=mono -appleDeveloperTeamID=<yourAppleDeveloperTeamID> -iOSProvisioningProfileID=<yourIosProvisionProfileID> -testResults /PerfTests/results/PerfBenchmark_Android_OpenGLES3_MtRendering_Mono .xml -logfile /PerfTests/logs/PerfBenchmark_Android_OpenGLES3_MtRendering_Mono_UnityLog .txt |
对于这二个示例,我们将引入了三到四个新命令行选项,使用IPrebuildSetup.Setup方法的命令行参数来帮助我们运行测试。
-runTests
该选项会告诉Unity Test Runner运行测试。
-testResults <pathToWritePerformanceTestResultsFile>
该选项会指定.xml文件的文件名和路径,Unity Test Runner在该文件保存性能测试结果。
-logfile <pathToWriteUnityEditorLogFile>
该选项会指定Unity编辑器所写入日志文件的文件名和路径。它是可选项,如果可以快速访问Unity编辑器日志文件,在调查失败结果和问题时,该选项会非常实用。
-batchmode
该选项会让Unity编辑器打开Headless模式。当只运行播放器性能测试且不需要打开Unity编辑器窗口时,我们会使用该选项。这可以在自动执行测试时节省时间。不使用该选项时,Unity编辑器会在执行测试前打开。
我们通常从命令行运行性能测试,在持续集成系统中使用batchmode。
示例:从命令行运行UnityPerformanceBenchmark测试
选择PlayMode时,在顶部打开Unity Test Runner窗口,我们会得到三个按钮:
- Run All:点击该按钮会在PlayMode标签页运行所有测试
- Run Selected:点击该按钮会运行所选测试或节点,包括节点下的所有测试
- Run all in player:点击该按钮会让Unity编辑器构建出构建设置中设定的播放器类型,并在该播放器中运行测试。
重要要求
在Unity编辑器中从Test Runner窗口运行性能测试。不会得到Unity Performance Benchmark Reporter需要的.xml文件。
如果想要在完成性能测试时得到结果.xml文件,需要在命令行加入-runTests命令行选项来启动Unity,再运行测试。请注意,当使用-runTests命令行选项运行Unity时,编辑器会打开并开始运行测试。
得到的.xml文件包含测试运行后的结果和元数据,我们会在Unity Performance Benchmark Reporter中使用这些结果来创建基准结果,并比较后续测试运行结果。
示例:在Unity编辑器运行性能测试
示例:在Unity Test Runner查看性能测试样本聚合
下面要讨论如何使用Unity Performance Benchmark Reporter来查看并对比结果。
使用Unity Performance Benchmark Reporter
Unity Performance Benchmark Reporter可以在带有图形可视化html报告中比较性能指标基线和后续性能指标。
Unity Performance Benchmark Reporter被构建为.NET Core 2.x程序集,所以它可以在支持.NET的不同平台上运行,例如:Windows、OSX等。因此,运行该工具,需要确保已安装.NET Core 2.x SDK。
执行Unity Performance Benchmark Reporter时,需要使用dotnet命令调用该程序集,命令如下:
dotnet UnityPerformanceBenchmarkReporter.dll --baseline=D:\UnityPerf\baseline.xml --results=D:\UnityPerf\results --reportdirpath=d:\UnityPerf |
该工具运行后,将创建名为UnityPerformanceBenchmark目录,其中包含html报告以及和报告相关的.css、.js和图片文件。打开该html报告可以查看.xml文件中所采集的性能指标可视化内容。
1、命令行选项
–results
该选项指定保存非基线.xml文件的目录路径,这些文件将包含在html报告中。
至少要给UnityPerformanceBenchmarkReporter.dll程序集传入一个–results值。它是唯一必需的字段。该命令行选项还能用于指定.xml非基线结果文件。此外,你可以通过重复使用该选项来指定多个路径或文件,命令如下:
--results=D:\UnityPerf\results --results=D:\UnityPerf\results.xml |
–baseline
该选项指定和其它结果对比的.xml文件的路径。
–reportdirpath
该选项指定Unity Performance Benchmark Reporter创建性能基准报告的目录路径。报告将创建在UnityPerformanceBenchmark子目录中。
如果未指定报告保存路径,将在调用UnityPerformanceBenchmarkReporter.dll的工作目录创建UnityPerformanceBenchmark子目录。
2、对比性能测试结果
下面来对比Performance Benchmark Reporter中的性能测试结果。
示例:在启用VR的Gear VR场景中尝试修改配置,以提高帧率。
下面有一个带有以下复杂性特征的Unity场景。
- 732个对象
- 95,898个三角形
- 69,740个顶点
我们针对该场景运行了Unity性能测试并采集了指标,这将帮助我们了解是否可以使用多通道立体渲染维持近60 FPS。
下面,对测试结果运行Performance Benchmark Reporter,发现得到的FPS接近30 FPS,是目标FPS的一半。
将配置改为单通道多视图渲染后,FPS提高到了37。如果我们希望让该场景在Gear VR上运行时不发生明显的掉帧现象,我们仍需要使FPS接近60。
最后,我们将尝试减少场景中旋转的立方体数量以提高FPS。在多次尝试后,我们将性能提升到55 FPS。但必须将场景中的对象数量从732个减少为31个,减少的对象数量相当多。
现在将它用作FPS基线和之后的基准,希望能提高它的性能。
建立基准并跟踪性能改动
建立基准可以具有多种含义,具体取决于项目。本文中指的是在Unity运行性能测试,我们将探讨建立结果的基线集,以及近期最好的性能指标集合,用来在做更改时对比后续结果。这些结果将成为我们的基准。
在上一部分中,我们为Gear VR使用了单通道多视图立体渲染的配置,并减少了场景对象数量,从而得到“可接受的”FPS。我们决定使用该测试结果作为基准。
下面让我们了解修改播放器配置时如何使用该基准。
示例:使用性能基准来检测配置改动造成的性能下降情况
我们将在场景中启用抗锯齿功能,使画面更平滑。Unity针对Android系统的默认质量设置会禁用抗锯齿功能,但我们想了解是否能启用该功能,并保持该Gear VR场景中可观的FPS。
首先在IPrebuildSetup.Setup方法中设置抗锯齿值为4。
QualitySettings.antiAliasing = 4; |
接下来,在启用Gear VR的Android手机上重新运行性能测试。然后,使用Unity Performance Benchmark Reporter来对比此次运行结果和新建立的基准结果。
重新配置Unity播放器并使用等级4的抗锯齿后,FPS下降为32,该值与之前项目开始时的FPS值差不多,那时场景中有732个对象。
现在尝试降低抗锯齿值,从而了解是否能恢复为之前可观的FPS值。所以我们将抗锯齿设为2,接着设为1,得到的结果如下图所示。
在重新配置的情况下,通过使用之前建立的性能基准,我们能够尝试修改Unity播放器设置,然后在提交更改前了解性能影响。
虽然仍使用默认方差阈值15%,抗锯齿值为1,FPS现在达到了49,对VR场景而言还是和理想的60 FPS差距较大。所以不会提交这些改动。
结语
Unity非常注重默认设置下的优秀性能。但Unity引擎只是这其中的一部分,为了让用户喜欢玩自己研发的游戏,更重要的是让他们在所有平台上享受到流畅和高性能的体验。例如:使用不会影响性能的SDK、驱动或Unity资源包也非常重要,从而使所有用户得到优秀的总体性能体验。
本文介绍了Unity Performance Testing Extension和Unity Performance Benchmark Reporter,它们用来轻松收集性能指标并用来创建基准。我们建议你尝试使用这些工具,了解它们能为性能工作做些什么。
我们在本文了解了:
1、如何使用Unity Test Runner编写性能测试,用来采样性能分析器和其它指标。
2、使用Unity Test Runner执行性能测试的不同方法。
3、如何使用Unity Performance Benchmark Reporter来分析并对比性能指标,在测试游戏性能时反复运行该工具。
为这些指标建立基线,然后使用基线为场景、游戏、SDK、驱动、资源包或其它Unity集成创建基准,这样能有效地了解改动造成的影响。