最近学习了下IL,寻思着小试牛刀,学以致用嘛。
所以就拿值类型与字符串连接来作了几组测试,使用Unity的Profiler提供的BeginSample和EndSample查看GC和cpu耗时,先把结果展示出来。
一、字符串连接代码与Profile展示
1.直接使用"+"连接一次,值类型不调用ToString方法
"+"连接一次无ToString
"+"连接一次无ToString的Profile
"+"连接无ToString发生装箱
2.直接使用"+"连接一次,值类型调用ToString方法
"+"一次有ToString
"+"一次有ToString的Profile
"+"一次有ToString无装箱
3.使用String.Format连接一次,值类型不调用ToString方法
String.Format一次无ToString
String.Format一次无ToString的Profile
4.使用String.Format连接一次,值类型调用ToString方法
String.Format一次有ToString
String.Format一次有ToString的Profile
5.使用StringBuilder连接一次(StringBuilder重载支持基本的值数据类型)
StringBuilder一次
一次StringBuilder的Profile
可以看出无论是使用"+"号连接还是使用String.Format方法,Time.time(值类型)没有调用ToString方法的,GC多出20B,cpu耗时都在0.01ms,无明显差别,StringBuilder因为重载了Append方法,可以看出与值类型的ToString后连接的方式GC一致。
接下来我们循环100次,再看看结果。
6.直接使用"+"连接100次,值类型不调用ToString方法
"+"连接100次无ToString
"+"连接100次无ToString的Profile
7.直接使用"+"连接100次,值类型调用ToString方法
"+"连接100次有ToString
"+"连接100次有ToString的Profile
8.使用String.Format连接100次,值类型不调用ToString方法
String.Format连接100次无ToString
String.Format连接100次无ToString的Profile
9.使用String.Format连接100次,值类型调用ToString方法
String.Format连接100次有ToString
String.Format连接100次有的Profile
10.使用StringBuilder连接100次(StringBuilder重载支持基本的值数据类型)
StringBuilder100次
StringBuilder100次的Profile
循环100次与单独一次GC几乎是没有差异的,约等于乘以100,而在耗时上"+"连接与StringBuilder是一致,似乎比String.Format耗时更短,因耗时在百分之一毫秒级,这里不做深究,但是可以看出值类型与字符串连接时,值类型的ToString方法调用与否会影响GC的开销。
二、IL代码分析
我们再来看一下IL代码。
"+"连接无ToString发生装箱
"+"连接有ToString无装箱
String.Format无ToString发生装箱
String.Format有ToString无装箱
StringBuilder无装箱
通过IL代码我们可以确定,额外的GC开销是因为值类型与字符串连接时,因为未调用ToString方法,会发生一次装箱操作,我们来看看String.Format方法的参数
String.Format方法
参数是作为object传入,所以会先从值类型转换到object类型,将值从堆栈中copy到托管堆,新建一个object类型的数据,再执行字符串连接操作,而值类型调用ToString方法后,直接就可以执行字符串连接操作。
三、测试结果图表展示
测试结果比较
GC结果比较
耗时结果比较
四、结论
1.值类型在与字符串连接时,无论是"+"连接,还是String.Format方法当值类型没有调用ToString方法时,都会发生装箱操作,带来额外的GC开销(cpu耗时开销不明显)
2.为了避免不必要的开销,值类型在与字符串连接时,值类型应该先调用一下ToString方法,养成好习惯。
最后,我也看到有些文章提到其实没有必要手动ToString,首先消耗并不多,而且也影响代码的美观,不过我觉得性能应该是第一位,没有好的性能,再美观的代码,用户也不会认可,用户不会去关心代码优不优雅,美不美观,用户只关心app是不是耗电,是不是卡顿。










网友评论