0
点赞
收藏
分享

微信扫一扫

【Unity】 HTFramework框架(四十)Debug的性能监控


更新日期:2021年4月22日。
Github源码:​​​[点我获取源码]​​​ Gitee源码:​​[点我获取源码]​​

索引

  • ​​C#代码性能监控​​
  • ​​使用​​
  • ​​Debug的性能监控模式​​
  • ​​监控代码片段​​
  • ​​监控方法​​
  • ​​适用场景​​

C#代码性能监控

C#代码运行时的性能消耗主要体现在两个方面,1是​​时间消耗​​​,2是​​空间消耗​​。

  • 时间消耗我们可以理解为某一段代码执行所消耗的CPU时间;
  • 空间消耗也即是消耗的内存空间,在C#中,内存空间分为栈空间堆空间,当然堆空间也叫做托管堆空间,因为堆这块已经托管给了CLR,他会负责开辟堆空间清理堆空间,所以我们主要关心的就是开辟清理这两个过程,因为在C#中这两个过程就是造成性能瓶颈的根本原因,也就是所谓的GC。

使用

Debug的性能监控模式

监控代码片段

一、我们可以使用如下方式监控一个代码片段的执行性能:

void Start()
{
List<int> ints = new List<int>(1000000);

//开始监控
Main.m_Debug.BeginMonitor("计算一百万次");

int test = 0;
foreach (var i in ints)
{
test += i;
}

//结束监控
MonitorData data = Main.m_Debug.EndMonitor();

//打印监控日志
data.ToString().Info();
}

运行结果如下:

注意:
1.产生的堆内存垃圾:也即是在托管堆上开辟的托管空间;
2.触发GC次数:当CLR清理托管堆时也即是一次GC;
如果你的代码开辟的空间越多,或者导致的GC次数越多,则证明他们产生性能瓶颈的可能性越大。

这里虽然foreach遍历执行了一百万次整型加法运算,但并未产生堆内存垃圾。

【Unity】 HTFramework框架(四十)Debug的性能监控_Test

二、然后我们改一下代码,将变量ints的赋值语句也放在监控代码段内:

void Start()
{
//开始监控
Main.m_Debug.BeginMonitor("计算一百万次");

List<int> ints = new List<int>(1000000);

int test = 0;
foreach (var i in ints)
{
test += i;
}

//结束监控
MonitorData data = Main.m_Debug.EndMonitor();

//打印监控日志
data.ToString().Info();
}

运行结果如下:

这里由于List是引用类型,new一个引用类型会在托管堆上开辟新的空间,也即是生成了一段内存垃圾(虽然他暂时还不是内存垃圾,但用完之后就是了),可见,一个整型的大小是4字节,一百万个整型正好是4百万字节,正好是4M字节!不过4M的垃圾并没有被CLR看在眼里,触发GC次数为0证明了他没有因为这点新增的垃圾就去启动回收操作。

【Unity】 HTFramework框架(四十)Debug的性能监控_List_02

三、然后我们再改一下代码,手动GC一次:

void Start()
{
//开始监控
Main.m_Debug.BeginMonitor("计算一百万次");

List<int> ints = new List<int>(1000000);

int test = 0;
foreach (var i in ints)
{
test += i;
}

//清理一次内存,将主动触发一次GC
Main.m_Resource.ClearMemory();

//结束监控
MonitorData data = Main.m_Debug.EndMonitor();

//打印监控日志
data.ToString().Info();
}

运行结果如下:

可以看到已经GC了一次,也即是回收了一次垃圾,但新增的堆内存垃圾仍然还有3M,很显然ints所指向的内存空间并没有被回收,CLR只是从其他地方收回了1M的空闲内存,因为CLR目前并不知道ints所指向的空间已经变成了垃圾,因为整个Start方法还没有结束,你还可以在后续调用ints,所以他还不是垃圾空间。

【Unity】 HTFramework框架(四十)Debug的性能监控_C#性能检测_03


四、然后我们再改一下代码,将代码片段放在一个方法里面:

void Start()
{
//开始监控
Main.m_Debug.BeginMonitor("计算一百万次");

Test();

//清理一次内存,将主动触发一次GC
Main.m_Resource.ClearMemory();

//结束监控
MonitorData data = Main.m_Debug.EndMonitor();

//打印监控日志
data.ToString().Info();
}

private void Test()
{
List<int> ints = new List<int>(1000000);

int test = 0;
foreach (var i in ints)
{
test += i;
}
}

运行结果如下:

可以看到,GC了一次后,ints所生成的4M垃圾空间已经被回收掉了,为什么这样就能回收掉呢?因为,ints的作用域变为了Test方法,在GC回收时,Test已经执行完毕,CLR收到明确指令:ints指向的已经是一块垃圾空间,可以回收。

注意:我们对比一下代码的时间消耗,未触发GC的执行时间为:0.0005秒,触发一次GC的执行时间为:0.0166秒,很明显,这多出来的近30倍执行时间,便是GC带来的性能损耗,这就好比使用时间的代价换来了空间!

【Unity】 HTFramework框架(四十)Debug的性能监控_Unity_04

终上所述,我们监控一个代码片段的执行效率时,重点关心的就是他的执行时间和产生了多少的堆内存垃圾,当然,如果在监控过程中触发了GC,那么最后产生的堆内存垃圾数量就不一定准确了,因为可能有一些垃圾已经被回收了,只不过,如果你的代码总是频繁的在触发GC,那么你一定得考虑重构他们了!

监控方法

当然,Debug也支持在监控模式中运行某一个方法,如下:

void Start()
{
//监控模式执行Test
MonitorData data = Main.m_Debug.MonitorExecute(Test);

//打印监控日志
data.ToString().Info();
}

private void Test()
{
List<int> ints = new List<int>(1000000);

int test = 0;
foreach (var i in ints)
{
test += i;
}
}

适用场景

一、我们可以使用性能监控器监测一些带来性能瓶颈的行为,比如,如下这个极具性能损耗的行为:

void Start()
{
//开始监控
MonitorData data = Main.m_Debug.MonitorExecute(Test, "字符串累加");

//打印监控日志
data.ToString().Info();
}

private void Test()
{
//string累加一万次
string test = "";
for (int i = 0; i < 10000; i++)
{
test += "string";
}
}

运行结果如下:

此时产生的堆内存垃圾记录已然不准确了,因为已经触发了79次GC!

【Unity】 HTFramework框架(四十)Debug的性能监控_C#性能检测_05


二、发现这个极大问题之后,我们即刻选择使用StringBuilder改进:

void Start()
{
//开始监控
MonitorData data = Main.m_Debug.MonitorExecute(Test, "StringBuilder累加");

//打印监控日志
data.ToString().Info();
}

private void Test()
{
//string累加一万次
StringBuilder builder = new StringBuilder();
string test = "";
for (int i = 0; i < 10000; i++)
{
builder.Append("string");
}
test = builder.ToString();
}

运行结果如下:

可以看到惊人的优化效果,只产生了262KB的垃圾,并且避免了触发GC!

【Unity】 HTFramework框架(四十)Debug的性能监控_C#性能检测_06


三、当然,我们也可以尝试使用string.Format来达到同样的效果:

void Start()
{
//开始监控
MonitorData data = Main.m_Debug.MonitorExecute(Test, "Format累加");

//打印监控日志
data.ToString().Info();
}

private void Test()
{
//同样是string累加一万次
string test = "";
for (int i = 0; i < 10000; i += 10)
{
test = string.Format("{0}{1}{1}{1}{1}{1}{1}{1}{1}{1}{1}", test, "string");
}
}

运行结果如下:

可以看到,string.Format虽然并不是最优手段,但他也同样能够带来一些优化效果。

【Unity】 HTFramework框架(四十)Debug的性能监控_Unity_07

总结,当你有一段比较复杂的靠人眼难以看出优劣的代码时,检测一下他运行时所消耗的时间和空间,是一个不错的优化指南!


举报

相关推荐

0 条评论