[Unity] 使用Profiler.BeginSample()定位性能热点

Unity客户端优化中最常使用的辅助优化工具是Profiler。使用Profiler,可以方便我们定位游戏程序的性能瓶颈,如定位游戏中单帧耗时过高的模块、定位游戏中产生GC较多的模块等等。

    尽管如此,在实际优化分析过程中,即使直接使用Profiler定位到游戏瓶颈的大致模块,也往往不能分析出更精确的瓶颈代码。例如,在实际开发过程中,我们发现Game.Update()这一模块特别耗时,其实也是无补于事。因为Game.Update()这一模块下层可能涵盖了网络交互、战斗、聊天等多个自定义模块,如果这些模块不是使用Unity自带的MonoBehavior.Update实现帧循环,我们就不能往更深层次模块进行分析了。

    在默认的情况下,Profiler仅为Unity自带的函数添加了性能采样点(如MonoBehavior的函数、Resources库函数等),但是Profiler无法直接对我们用户的代码进行采样分析。

    使用Profiler评估客户端性能时,推荐使用Profiler提供的性能采样接口,来更精确地分析定位客户端存在的性能问题。

 

    优点:使用Profiler提供的性能采样接口,最大的优点是可以更深层次地分析用户代码的性能热点,避免定位到大致模块后,无法继续往下分析,只能通过其他方式(如代码审查)继续优化的尴尬。


    举个例子,如图:


使用BeginSample、EndSample配对,可以有效定位用户自己编写的代码


    由上图可见,在没有使用Profiler.BeginSample()定位的情况下,Profiler只能分析到Game.Update()模块比较耗时,往下只能看到耗时大头在Loading.ReadObject这一点上,但是由于逻辑无关,我们无法分析导致加载耗时的模块具体发生在Game.Update的哪一子模块。

    而使用Profiler.BeginSample()定位后,我们可以有层次性地发现,单帧耗时过大的瓶颈耗点位于 "Game.Update() -> GameNewWork.Update() -> HandleIO"。


    除此之外,我封装了一套自己的接口,代码在本文最后面。之所以封装一层,原因如下:

        1、提供Profiler性能采样开关,可随时关闭

        2、提供字符串格式化功能,可在Profiler中显示自定义的文本,方便定位问题(使用时需要谨慎,后文叙述

 

关于格式化字符串

有时候光知道热点代码位置是不够的,还需要知道代码中变量数据。

例如下图OnRecv函数,代码只是根据cmd参数,获取已注册的句柄,然后调用。如果没有格式化功能,我们只能知道代码最终执行到这一环节,而不能准确定位该句柄代表的是哪个函数。使用格式化功能,把对应句柄的函数名打印出来,我们就可以知道热点对应的是哪一个函数。

    

 

慎用格式化字符串


以上代码会较高频率地触发垃圾回收(GC.Collect()),是不是逻辑代码有问题?

测试发现,CurrentState.Update片段几乎均为每帧0B的开销,而其他代码也不存在内存分配现象,GC.Collect()是ProfilerSample.BeginSample(format)导致的。

需要注意格式化字符串本身会带来内存分配开销,使用格式化字符串采样接口时需考虑自身对代码带来的影响。

 

使用经验:

1、在可能的热点函数上插入性能采样代码,建议编译手机版本来分析结果。当然,在熟悉代码的前提下,可以方便使用PC测试分析GCAlloc等问题。原因如下:

    1)PC性能相对太好,一些手机上的瓶颈函数在PC上几乎不耗时,导致无法准确分析;

    2)一些代码,特别是插件代码,PC和手机的执行流程不同,PC分析的结果不能准确表明手机也是同样结果。

2、在插入性能采样代码时,特别留意函数中是否存在多个return的现象。这些return如果没有处理好,就有可能导致性能采样的Begin和End不匹配,导致Profiler显示的结果有误。

3、对于协程函数,BeginSample、EndSample之间注意不能存在yeild return null,否则可能导致Unity客户端卡死、手机卡死等现象。个人分析:Begin和End配对分析的是单帧结果,出现yeild return null代表该区间将会分两帧甚至多帧完成。


封装好的性能采样接口代码:

using UnityEngine;
using System;
public class ProfilerSample {
    public static bool EnableProfilerSample = true;
    public static bool EnableFormatStringOutput = true;// 是否允许BeginSample的代码段名字使用格式化字符串(格式化字符串本身会带来内存开销)
    public static void BeginSample(string name) {
#if ENABLE_PROFILER
        if(EnableProfilerSample){
           Profiler.BeginSample(name);
        }
#endif
    }
    public static void BeginSample(string formatName, params object[] args) {
#if ENABLE_PROFILER
        if(EnableProfilerSample) {
           // 必要时很有用,但string.Format本身会产生GC Alloc,需要慎用
           if (EnableFormatStringOutput)
               Profiler.BeginSample(string.Format(formatName, args));
           else
               Profiler.BeginSample(formatName);
        }
#endif
    }
    public static void EndSample() {
#if ENABLE_PROFILER
        if(EnableProfilerSample) {
           Profiler.EndSample();
        }
#endif
    }
}


  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity 是一款非常强大的跨平台游戏引擎,它提供了丰富的功能和工具来帮助游戏开发者创建高品质的游戏。而使用 Newtonsoft.dll 则是为了在 Unity 中更方便地处理 JSON 数据。 Newtonsoft.dll 是一个用于处理 JSON 数据的第三方库,也称作 Newtonsoft.Json。在 Unity使用它主要有以下几个原因: 1. 强大的功能:Newtonsoft.Json 提供了许多方便的方法和工具来序列化(将对象转换为 JSON)和反序列化(将 JSON 转换为对象)数据。这使得在 Unity 中处理 JSON 数据变得非常简单和高效。 2. 跨平台兼容性:该库已经被广泛应用于各种平台和编程语言中,并且与 Unity 兼容性良好。这意味着无论您将游戏发布到哪个平台或使用哪种开发环境,都可以轻松地使用 Newtonsoft.Json 进行 JSON 数据处理。 3. 第三方支持和社区:使用 Newtonsoft.Json 有着丰富的文档和教程资源,以及庞大的开发社区。这些资源可以帮助您更好地理解和使用 Newtonsoft.Json,在开发过程中得到有价值的支持和建议。 4. 高性能和灵活性:Newtonsoft.Json 提供了高性能的 JSON 数据处理能力,并具有许多可配置的选项。这使得您可以根据自己的需求进行定制,并在高要求的游戏开发中实现更好的性能和用户体验。 综上所述,Unity 使用 Newtonsoft.Json 主要是为了方便地处理 JSON 数据,它提供了强大的功能、跨平台兼容性、第三方支持和灵活性。这使得在 Unity 中处理 JSON 数据变得更加简单和高效,为游戏开发者提供了更多的选择和优化的空间。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值