29.Unity的Time类计时

29.性能优化-CPU-脚本-计时判断代码执行效率-Unity的Time类


29.1 知识点

为什么要计时判断代码执行效率

游戏开发中性能资源有限,若某段代码耗时过长,会导致帧率下降、卡顿或掉帧,从而影响玩家体验。通过计时可以更细颗粒度地找出是哪段代码导致帧率下降。

虽然 Profiler 能帮助排查耗时元凶,但手动实现代码计时仍有其价值:

  1. Profiler 在正式发布版中一般不包含
    手动计时代码可以在正式版本中用于日志上报、自动分析等。

  2. Profiler 不太适合精细比较同一段代码在不同算法下的微秒级表现
    手动计时代码可以针对性地做细颗粒度比较。

  3. Profiler 不太适合做批量性能测试、基准对比
    手动计时可以把一个算法跑指定次数,计算平均耗时、最大耗时等。

等等。

计时判断代码执行效率的基本原理

原理:记录一段代码执行前后的时间差,从而估算其运行耗时。
说人话耗时 = 结束时间 - 开始时间

对应代码形式如下:

float startTime = 获取开始时间;
// 执行要测的逻辑
float endTime = 获取结束时间;
float spendTime = endTime - startTime;  // 耗时

测试建议

  1. 多次执行、取平均值,减少偶然波动。
  2. 不要在初始化阶段测,等消耗稳定后(例如通过按键)再触发测试。
  3. 不要在测试循环内使用 Unity 控制台打印相关 API;应在测试结束后再输出,或只记录数据。

利用Unity的Time类计时

利用 Time 类中「游戏启动以来的时间」进行计算,常用 **Time.realtimeSinceStartup**(不受 Time.timeScale 影响)。单位为秒、带小数,精度约毫秒级,适合一般耗时统计。

示例代码:

float startTime = Time.realtimeSinceStartup;
// 执行要测的逻辑(不要在这里面用 Debug.Log,会拉高耗时)
// ... 你的代码 ...
float spendTime = (Time.realtimeSinceStartup - startTime) * 1000f;  // 转为毫秒,1s = 1000ms
Util.Log("耗时 " + spendTime + " ms");

要点:在测试循环外再输出结果,或通过封装好的 Util.Log 等记录,避免在循环内直接 Debug.Log 影响测量。

利用C#机制封装一个计时类

可以利用 C# 的 using 语句块using 包裹的对象在离开作用域时会自动调用销毁逻辑(执行 Dispose),因此可以在构造函数里记录开始时间、在 Dispose 里计算耗时并输出,这样写出来的计时代码简洁、不易漏写结束计时。

实现CustomUnityTimer 实现 IDisposable,构造时记录测试名称、测试次数和开始时间,Dispose 时用 Time.realtimeSinceStartup 算总耗时并转为毫秒,再输出总耗时和平均每次耗时。

using UnityEngine;
using System;

public class CustomUnityTimer : IDisposable
{
    private string _name;     // 本次测试的名字
    private uint _num;         // 执行逻辑多少次
    private float _startTime; // 计时开始时间

    public CustomUnityTimer(string name, uint num)
    {
        _name = name;
        _num = num;
        if (_num == 0) _num = 1;  // 至少执行 1 次
        _startTime = Time.realtimeSinceStartup;
    }

    public void Dispose()
    {
        float spendTime = (Time.realtimeSinceStartup - _startTime) * 1000f;  // 转为 ms
        Util.Log(string.Format("{0}计时结束,共耗时{1}ms,一共测试{2}次,每次平均耗时{3}ms",
            _name, spendTime, _num, spendTime / _num));
    }
}

使用方式:用 using 包裹要测的逻辑即可;离开作用域时自动调用 Dispose 并打印结果。

uint num = 10000;  // 测试次数
using (new CustomUnityTimer("某段逻辑耗时测试", num))
{
    for (int i = 0; i < num; i++)
    {
        // 换成自己要测的函数或代码逻辑
        DoSomething();
    }
}
// 离开 using 时自动调用 Dispose,输出:共耗时 xx ms,平均每次 xx ms

建议在消耗稳定后通过按键等触发上述代码,而不是在 Start 里直接跑,以便得到更稳定的测试结果。


29.2 知识点代码

CustomUnityTimer.cs(计时封装示例)

using UnityEngine;
using System;

/// <summary> 用于对逻辑执行计时的性能分析类 </summary>
public class CustomUnityTimer : IDisposable
{
    private string _name;   /* 本次测试的名字 */
    private uint _num;      /* 执行逻辑多少次 */
    private float _startTime; /* 计时开始时间 */

    public CustomUnityTimer(string name, uint num)
    {
        _name = name;
        _num = num;
        if (_num == 0)
            _num = 1;  /* 避免传入 0,至少执行 1 次 */
        _startTime = Time.realtimeSinceStartup;
    }

    public void Dispose()
    {
        float spendTime = (Time.realtimeSinceStartup - _startTime) * 1000f;  /* 转为 ms */
        Util.Log(string.Format("{0}计时结束,共耗时{1}ms,一共测试{2}次,每次平均耗时{3}ms",
            _name, spendTime, _num, spendTime / _num));
    }
}

Lesson29_性能优化_CPU_脚本_计时判断代码执行效率_Unity的Time类.cs

using UnityEngine;

public class Lesson29_性能优化_CPU_脚本_计时判断代码执行效率_Unity的Time类 : MonoBehaviour
{
    void Start()
    {
        #region 知识点一 为什么要计时判断代码执行效率

        /*
         * 游戏开发中性能资源有限,某段代码耗时过长会导致帧率下降、卡顿或掉帧,影响体验。
         * 通过计时可以更细颗粒度地找出是哪段代码导致帧率下降。
         * 手动计时的好处:发布版可做日志上报与自动分析;可做微秒级、细颗粒度比较;
         * 可跑指定次数做批量测试,算平均/最大耗时等。
         */

        #endregion

        #region 知识点二 计时判断代码执行效率的基本原理

        /*
         * 原理:记录代码执行前后的时间差,估算运行耗时。耗时 = 结束时间 - 开始时间。
         * 测试建议:多次执行取平均;不在初始化时测,待稳定后通过按键触发;不在测试循环内用控制台打印,测试后再输出或只记录数据。
         */

        #endregion

        #region 知识点三 利用Unity的Time类计时

        /*
         * 使用 Time.realtimeSinceStartup(不受 Time.timeScale 影响),单位秒、带小数,毫秒级精度。
         * 开始时记录 startTime,逻辑结束后 (Time.realtimeSinceStartup - startTime) * 1000f 即毫秒数。
         */
        float startTime = Time.realtimeSinceStartup;
        /* 执行一段逻辑代码 */
        Debug.Log("12312312");
        float spendTime = (Time.realtimeSinceStartup - startTime) * 1000f;
        Util.Log("Debug打印耗时" + spendTime + "ms");

        #endregion

        #region 知识点四 利用C#机制封装一个计时类

        /* 使用 C# 的 using 语句块,离开作用域时自动调用 Dispose,在构造时记开始时间、Dispose 时算耗时并输出 */

        #endregion
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            uint num = 10000;
            using (new CustomUnityTimer("Debug耗时测试", num))
            {
                for (int i = 0; i < num; i++)
                {
                    /* 换成自己要测的函数或代码逻辑 */
                    Debug.Log("123123");
                }
            }
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏