Back to Top

Unity foreach 造成额外的GC开销

Unity的foreach会造成额外的GC开销,但一直没有细究,今早看了作为Unity3D的脚本而言,c#中for是否真的比foreach效率更高? - 回答作者:王剑飞明白了其产生的原因,于是做此笔记。

例如针对Dictionary<string, int> TestContainer进行测试:

产生额外GC Alloc的原因:

void TestListForeach()
{
	foreach (var i in TestContainer)
	{
		CountString(i.Key);
	}
}

会产生额外的GC Alloc,原因是这段代码等同于:

void TestListNoForeachWithUsing()
{
	using(var i = TestContainer.GetEnumerator())
	{
		while(i.MoveNext())
		{
			CountString(i.Current.Key);
		}
	}
}

当using结束进行Dispose时,因为IEnumerator<T>是结构体,进行了一次装箱操作。

不产生额外GC Alloc的等价写法:

void TestListNoForeachSafe()
{
	var i = TestContainer.GetEnumerator();
	try
	{
		while(i.MoveNext())
		{
			CountString(i.Current.Key);
		}
	}
	finally
	{
		i.Dispose();
	}
}

这个写法写起来挺拗口的,如果不调用IEnumerator<T>.Dispose()会好很多:

void TestListNoForeach()
{
	var i = TestContainer.GetEnumerator();
	while(i.MoveNext())
	{
		CountString(i.Current.Key);
	}
}

那Dictionary.Dispose做了什么呢?

 
// 摘自Unity Mono的 mcs/class/corlib/System.Collections.Generic/Dictionary.cs 文件
public struct Enumerator : IEnumerator <T>, IDisposable {
	Dictionary<TKey, TValue> dictionary;
	// ...
	public void Dispose ()
	{
		dictionary = null;
	}
}

通过以上代码,我认为针对Dictionary是可以不调用Dispose的。当然作为通用流程还是要调用的,如果想采用简单写法,必须明白Dispose做了什么。

结论

因为foreach比其替代写法,清晰明了太多,建议只用在一些不常调用的函数上。 可以先使用foreach开发,针对Profiler进行定点的优化, 为了项目不易出错,改写的代码,采用TestListNoForeachSafe写法。