一、简介 效率包括了代码的 GC 大小与内存大小,执行速度等等。其中执行速度不是关注的重点。
Mono 项目主页:http://www.mono-project.com/
ILSPY 反编译工具,功能比 NET.Reflector 好用多了。具体的使用方法可以百度。 简单的过程: 把编译的 dll(\Library\ScriptAssemblies 目录下的 Assembly-CSharp.dll)拖入左边窗口,选择需要查看的类以及函数,然后选择翻译的语言,具体如下
IL 代码 举个简单的例子,来说明 IL 代码把,具体可查阅 msdn 文档。 C#代码:
object objValue = 4;
int value = (int)objValue;
生成的 IL 代码如下:(注释有详细的说明)
二、GC 与内存篇 for 与 foreach 真相 目的 在 Mono 下,研究两种不同方式的 GC 情况。 环境 同简介中的环境 测试代码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class TestCodPerfomanceScript : MonoBehaviour
{
// Use this for initialization List<int> mData = new List<int>();
void Start()
{
mData.Add(1); //可有 n 个元素,n 也可以为 0
}
void testForForeach_foreach()
{
foreach (var e in mData)
{
//Debug.Log(e);
}
}
void testForForeach_for()
{
int len = mData.Count;
for (int i = 0; i < len; i++)
{
}
}
void testForForeach_nonforeach()
{
var e = mData.GetEnumerator();
while (e.MoveNext())
{
}
}
// Update is called once per frame
void Update()
{
//testForForeach_foreach();
//testForForeach_for(); testForForeach_nonforeach();
}
}
测试结果
数据分析 反编译->C#代码
从代码中,我们可知 foreach 中,编译器加了不少代码。从这里应该是有发生过装箱的操作。从对 testForForeach_nonforeach 函数的反编译 C#代码来看,应该是 using 那块产生装箱操作。继续查看反编译的 IL 代码。
反编译->IL 代码 testForForeach_foreach 的 IL 代码
从图中可以清楚中的看到有一次装箱过程。
数据结论 可以显然可知,foreach 每次有 40B 的 GC 产生(这个是 Mono 的一个 bug)。其余两种方式不产生 GC。 产生 GC 的根本原因是使用了 using 语句。(GetEnumerator()返回值类型,在 using 中装箱了) 那么,我们写一段测试代码,在去验证一下:
void testForForeach_nonforeachUsing()
{
using (var e = mData.GetEnumerator())
{
while (e.MoveNext())
{
}
}
}
得到的数据是
意义 所以,在目前的项目中,foreach 还是有 GC 的。(不管是 IList 还是 ArrayList 都会有 GC) 具体见测试数据
建议项目中采用testForForeach_for()和testForForeach_nonforeach()的写法。尤其是在 update 或 LateUpdate 中。
在处理 Dictionary 遍历的时候,我们可以这样:
var enumerator = m_Dictionary.GetEnumerator();
while (enumerator.MoveNext())
{
var element = enumerator.Current; element.Value.UpdateComponent(deltaTime);
}
字符串拼接真相 目的 在 Mono 下,研究两种常用的字符串拼接的内存与 GC 问题。
环境 同简介中的环境 测试代码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Text;
public class TestCodPerfomanceStringBuilderScript : MonoBehaviour
{
// Use this for initialization
void Start()
{
}
void testStringBuilder_plus()
{
string test = "abc" + "efg" + "h";
int index = test.Length;
}
void testStringBuilder_StringBuilder()
{
StringBuilder testData = new StringBuilder(); testData.Append("abc"); testData.Append("efg"); testData.Append("h");
string test = testData.ToString();
}
// Update is called once per frame
void Update ()
{
testStringBuilder_plus();
//testStringBuilder_StringBuilder();
}
}
测试结果
数据分析 反编译->C#代码
反编译->IL 代码
分析 从上面的测试用例得出, testStringBuilder_plus () 不产生 GC ,而 testStringBuilder_StringBuilder()产生 GC,和我们之前的理解有些偏差。是不是我们的测试用例还不够全,我们把 3 次相加变成 300 次试试。 代码如下:
void testStringBuilder_plus_More()
{
string test = "abc" + "efg" + "h";
for (int i = 0; i < 300; i++)
{
test += "testStringBuilder_plus_testStringBuilder_plus_More";
}
int index = test.Length;
}
void testStringBuilder_StringBuilder_More()
{
StringBuilder testData = new StringBuilder();
for (int i = 0; i < 300; i++)
{
testData.Append("testStringBuilder_plus_testString Builder_plus_More");
}
string test = testData.ToString();
}
测试结果如下:
反编译代码分别如下:
testStringBuilder_plus_More()
testStringBuilder_StringBuilder_More()
在 300 次的数量下面,结果很明显。
数据结论 不同的使用场景结果是不同的,+ 号在几次连接中没有产生 GC。而在大量的连接过程中,产生大量的 GC。 意义 连接次数只有几次(10 以内),此时应该直接用 + 号连接,不产生 GC。实际上,编译器已经做了优化。 其余的使用 StringBuilder , StringBuilder 内部 Buffer 的缺省值为 16 ,按 StringBuilder 的使用场景,Buffer 肯定得重新分配。经验值一般用 256 作 为 Buffer 的初值。当然,如果能计算出最终生成字符串长度的话,则应该按这个值来设定 Buffer 的初值。使用 new StringBuilder(256) 就将 Buffer 的初始 长度设为了 256。