目录
1.2.7 代码片段的针对性分析
需要进一步分析的实际问题
需求确定问题是否可以重现,在什么情况下出现性能瓶颈,以及问题代码块中问题的确切来源。
分析任务有用的技术
- 从脚本代码控制Profiler
- 自定义定时和日志记录方法
1.Profiler脚本控制
可以通过Profiler类在脚本代码中控制Profiler。最重要的方法是在运行时激活和禁用分析功能的分隔符方法,BeginSample()和EndSample()。
分隔符方法仅在开发构建过程中编译,这通常称为非操作或非操作代码。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ProfilerTest : MonoBehaviour
{
public int count1=10000;
public int count2=1000;
public int count3=1000000;
// Start is called before the first frame update
void Start()
{
DoSomethingCompletelyStupid(count1);
}
// Update is called once per frame
void Update()
{
DoSomethingCompletelyStupid(count2);
}
[ContextMenu("DoTest")]
private void DoTest(){
DoSomethingCompletelyStupid(count3);
}
private void DoSomethingCompletelyStupid(int count){
UnityEngine.Profiling.Profiler.BeginSample("My Profiler Sample");
List<int> listOfInts=new List<int>();
for(int i=0;i<count;i++)
{
listOfInts.Add(i);
}
UnityEngine.Profiling.Profiler.EndSample();
}
}
没有使用Profiler.BeginSample之前,在Profiler里面只能看到Update()深度的。
另外,在循环里面不要使用Profiler.BeginSample,不然很影响性能。Profiler里面看到的分析数据也没有意义。
而且我也就这一个脚本,没有任何模型,使用Deep Profile结果内存消耗好几G,帧率也就5。这个DeepProfile功能应该多一些选项,能够选择分析哪些函数。
虽然这个Profiler.BeginSample也就是这个作用了,手动添加分析方法。
2.自定义CPU分析
对代码执行定制的分析和日志记录。
了解一些对代码执行独立分析的技术是一项有用的技能。
精度很重要,提高精度的一个有效方法是多次运行相同的测试。
using System;
using System.Diagnostics;
public class CustomTimer : IDisposable
{
private string _timerName;
private int _numTests;
private Stopwatch _watch;
public CustomTimer(string timerName,int numTests){
_timerName=timerName;
_numTests=numTests;
if(_numTests<=0){
_numTests=1;
}
_watch=Stopwatch.StartNew();
}
public void Dispose(){
_watch.Stop();
float ms=_watch.ElapsedMilliseconds;
UnityEngine.Debug.Log(string.Format(
"{0} finished : {1:0.00} mimiseconds total,{2:0.000000} milliseconds per-test for {3} tests"
,_timerName,ms,ms/_numTests,_numTests));
}
}
public int numTests=1000;
void Start()
{
using(new CustomTimer("MyTest",numTests))
{
for(int i=0;i<numTests;++i){
DoSomethingCompletelyStupid(count2);
}
}
}
MyTest finished : 1652.00 mimiseconds total,1.652000 milliseconds per-test for 1000 tests
注意点:
1.不同调用的处理时间存在很大差异。
2.重复请求相同的内存块将导致认为提高缓存命中率。
3.实时(JIT)编译只影响方法的第一次调用
另一个问题是应用程序的预热时间,启动成本。
如果想要准确地测试,任何运行时测试都应该在应用程序达到稳定状态之后才开始。
可以将目标代码块打包到Input.GetKeyDown()检查中,以便在调用它时进行控制。
Unity的可控制体窗口日志记录机制是非常昂贵的。因此,不应在分析测试中(或在进行游戏期间)使用这些日志方法。如果绝对需要详细的分析数据,打印出大量的各种信息,则明智之举是缓存日志数据,将它们全部打印出来。
使用+操作符生成自定义string对象会导致今日的内存分配量,令系统进行垃圾回收。可以使用string.Format(),或者StringBuilder吧。
1.3 关于分析的思考
考虑性能优化的一种方式是剥离那些消耗宝贵资源的不必要任务。可以最小化任何浪费,来最大化生产率。
如何正确使用任何一种数据收集工具的建议:
- 理解Profiler工具
- 减少干扰
- 关注问题
1.3.1 理解Profiler工具
应该始终注意时间轴视图中显示的图形的相对性质。不要被Profiler工具骗了,以为大的峰值总是不好的。
有时候无论怎么改进有问题的代码块,最终用户也可能永远不会注意到它。
1.3.2 减少干扰
干扰的经典定义是没有意义的数据。
在停用特定对象时,性能突然变得更容易接受,那么显然该对象与问题的根源有关。
1.3.3 关注问题
专注是指不让自己因无关紧要的任务和徒劳无益的追求而分心。
在数据采样期间实现的任何更改有时可能导致我们跟踪应用程序中不存在的bug。如果现有的调查中不断出现新的瓶颈,它们可能是测试代码引入的瓶颈,而不是新暴露出来的问题。