今天我也偷个懒,来翻译一篇不错的博文,不过本人英语水平有限,翻译不当之处万望指教,在下当即改正,原文地址《Reducing Memory Usage in Unity, C# and .NET/Mono》(需要翻墙)
Unity的iOS版使用的是早期版本的Mono的堆管理器,而这个管理器并不会进行碎片管理,即使当你的堆充满碎片,也只是重新new一个给你。我印象里Unity的开发人员在尝试设计一套新的堆管理器来解决这个问题。但是目前为止即使你的游戏没有内存泄漏,也会因为不断增长的内存消耗而有当机的隐患。
C#语言一大好处便是可以在快速的编写功能代码同时还能拥有很好的可读性,但是另一个不可避免的问题则是C#语言的机制会让你在不知不觉中编写产生大量需要垃圾回收的代码,而解决这一问题的唯一办法就是减少你对堆的使用,这里我列出了一些可以规避这一现象的注意事项。
最终的效果会让你的C#代码看上去更像C++代码,可能会有一些C#语言的便利你将无法享受到,但是得到的却是性能上提升,可以让你的程序跑的更快~
要做到的这一点,Unity profiler是个很好的工具来帮你发现问题。打开profiler,运行游戏,选择CPU profiler,点击GC Alloc一列,来寻找问题最严重的函数,我们可以从这些肇事者们来入手寻找提升性能的空间。
- 避免使用foreach() : 因为它会调用GetEnumerator(),从而在循环的过程中在堆中产生enumerator对象,而这些对象并无他用,所以我们应当使用传统的for函数来完成工作以避免额外的内存负担。
- 避免使用strings : 在.NET中strings是不可变长并且是在堆中申请的。而且你不能像在C语言中的方式一样去修改它。对于UI,使用StringBuilder来创建strings是一种比较有效率的做法,而且应当在尽量晚的时候才进行转换。这并不影响你使用它们作为关键字索引来使用,因为游标是会找到内存中的实例所在,但是请避免过多的修改它们。
- 使用structs : 在mono中structs是在栈中申请的。如果你有一个工具类同时仅仅是在局部范围使用的,那么你可以考虑把它变成struct。要记住structs是传值的,所以需要通过引用来避免额外的拷贝花销。
- 使用structs来代替运行范围内的固定数组 : 如果你的类的方法中有一些固定大小的数组,那么不妨使用structs或者成员数组来代替它,这样不必每次运行函数都申请变量,尤其是如果这些方法需要被调用成百上千遍。
- 把List作为引用参数传入函数而不是新创建一个再返回 : 听上去仿佛没有节约什么,因为传入的List同样需要申请内存对吗?但我们这么做的原因是为了下一个看上去并不漂亮的优化。
- 使用成员变量来代替高频率出现的局部变量 : 如果你的函数每次运行时都需要一个很大的List,那么不妨将这个List设为成员变量,这样List可以独立于函数运行,在需要时你可以通过Clear()函数来清空List,而在C#中Clear()并不会真正的将你的内存空间删除,虽然会有碍于代码的美观,但是可以极大减轻性能上的负担。
- 避免IEnumerable扩展函数 : 无可厚非IEnumerable扩展函数虽然使用方便,但是却会带来更多的内存负担。所以和foreach()同样道理,我们应当尽力避免在代码中使用IEnumerable<>接口,你可以用IList<>来代替。
- 减少使用函数指针 : 使用委托或者Func<>会为你的程序带来新的内存分配,可是实际上却也找不到更好的办法来实现同样的功能,尤其是它会为你程序在解耦带来巨大的好处,但是总而言之要尽量精简。
- 要注意cloned材质 : 如果你想获取renderer的material属性,那么你需要注意即使你不想修改任何东西,系统也会复制一份material给你,而且这个material不是自动回收的,只有在你切换场景或者手动调用Resources.UnloadUnusedAssets()才会被释放。如果你不需要修改材质,请通过myRenderer.sharedMaterial来访问材质。