# 1. 为什么需要?

这个性能工具的作用是,能够让我们自己知道,每个阶段分别耗时多少,并且哪个阶段最慢,用了哪些步骤。

# 2. 核心

这个轻量级性能分析工具的本质,其实就是一个树形结构的计时器。每个节点都是一个 Profiler 的实例,可以

  • 记录这段任务的耗时
  • 记录拥有哪些子节点
  • 输出统计结果

# 3. 文件逻辑组成

# 3.1 静态成员

1
2
3
4
5
6
7
8
// 全局计时器(Stopwatch 是高精度计时器)  
private static readonly Stopwatch ms_Stopwatch = Stopwatch.StartNew();

// 用于生成最终的文本结果
private static readonly StringBuilder ms_StringBuilder = new StringBuilder();

// 用于遍历输出树形结构的栈
private static readonly List<Profiler> ms_Stack = new List<Profiler>();

# 3.2 实例成员

1
2
3
4
5
6
private List<Profiler> m_children;  // 子 Profiler,用于嵌套分析  
private string m_Name; // 当前 Profiler 名称
private int m_Level; // 层级深度(根为 0)
private long m_Timestamp; // 启动时刻的时间戳
private long m_Time; // 累积耗时(tick)
private int m_Count; // Start/Stop 次数(调用次数)

其实可以简单理解为,这些字段就是 “树节点 + 计时器” 的组合,即每个节点都拥有自己的计时,并且拥有各个的子节点。

# 4. 主要方法解析

# 4.1 构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Profiler(string name)  
{
m_children = null;
m_Name = name;
m_Level = 0;
m_Timestamp = -1;
m_Time = 0;
m_Count = 0;
}

/// <summary>
/// 创建子节点(内部使用)
/// </summary>
private Profiler(string name, int level) : this(name)
{
m_Level = level;
}

构造函数用于创建新的节点,并且在下面为了区分子节点,重载了一个方法

# 4.2 CreateChild

1
2
3
4
5
6
7
8
9
10
11
12
/// <summary>  
/// 创建子 Profiler(常用于分阶段性能统计)
/// </summary>
public Profiler CreateChild(string name)
{
if (m_children == null)
{ m_children = new List<Profiler>();
}
Profiler profiler = new Profiler(name, m_Level + 1);
m_children.Add(profiler);
return profiler;
}

# 4.3 计时器核心操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/// <summary>  
/// 重置当前及子节点的统计数据
/// </summary>
public void Reset()
{
m_Timestamp = -1;
m_Time = 0;
m_Count = 0;

if (m_children == null)
return;

// 递归重置所有子节点
for (int i = 0; i < m_children.Count; ++i)
{ m_children[i].Reset();
}}

/// <summary>
/// 开始计时(Start)
/// </summary>
public void Start()
{
// 防止重复开始
if (m_Timestamp != -1)
{ throw new Exception($"{nameof(Profiler)} {nameof(Start)} error, repeat start, name: {m_Name}");
}
// 记录当前计时器刻度
m_Timestamp = ms_Stopwatch.ElapsedTicks;
}

/// <summary>
/// 重置后重新开始计时
/// </summary>
public void Restart()
{
Reset();
Start();
}

/// <summary>
/// 停止计时(Stop)
/// </summary>
public void Stop()
{
// 防止未开始就停止
if (m_Timestamp == -1)
{ throw new Exception($"{nameof(Profiler)} {nameof(Stop)} error, repeat stop, name: {m_Name}");
}
// 计算耗时(当前时间 - 启动时间)
m_Time += ms_Stopwatch.ElapsedTicks - m_Timestamp;

// 记录调用次数
m_Count += 1;

// 重置时间戳,防止误用
m_Timestamp = -1;
}

# 4.4 核心:格式化操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/// <summary>  
/// 以树形结构格式化当前节点及其统计信息。
/// 内部使用 StringBuilder 构造结果。
/// </summary>
private void Format()
{
ms_StringBuilder.AppendLine();

// 根据层级添加缩进符号
for (int i = 0; i < m_Level; ++i)
{ ms_StringBuilder.Append(i < m_Level - 1 ? "| " : "|--");
}
ms_StringBuilder.Append(m_Name);

if (m_Count <= 0)
return;

// 输出统计结果
ms_StringBuilder.Append(" [");
ms_StringBuilder.Append("Count: ");
ms_StringBuilder.Append(m_Count);
ms_StringBuilder.Append(", Time: ");

// 转换 tick 为毫秒、秒、分钟
float ms = (float)m_Time / TimeSpan.TicksPerMillisecond;
float sec = (float)m_Time / TimeSpan.TicksPerSecond;
float min = (float)m_Time / TimeSpan.TicksPerMinute;

ms_StringBuilder.Append($"{ms:F2} 毫秒, ");
ms_StringBuilder.Append($"{sec:F2} 秒, ");
ms_StringBuilder.Append($"{min:F4} 分钟");
ms_StringBuilder.Append("]");
}

// --------------------------【文本输出】--------------------------

/// <summary>
/// 将当前 Profiler(含子节点)格式化成文本。
/// 用于 Debug.Log 输出。
/// </summary>
public override string ToString()
{
ms_StringBuilder.Clear();
ms_Stack.Clear();

// 使用栈进行深度遍历
ms_Stack.Add(this);

while (ms_Stack.Count > 0)
{ int index = ms_Stack.Count - 1;
Profiler profiler = ms_Stack[index];
ms_Stack.RemoveAt(index);

profiler.Format();

// 如果有子节点,则按逆序入栈
List<Profiler> children = profiler.m_children;
if (children == null)
continue;

for (int i = children.Count - 1; i >= 0; --i)
{ ms_Stack.Add(children[i]);
} }
return ms_StringBuilder.ToString();
}