在解决资源引起的性能之后,我们着手解决代码层的问题
UNITY3D/MONO的坑是比较多的,前期制作好代码规范,后面会少走很多弯路。
一、MonoC#篇
1、不要使用foreach
每个foreach调用,大概会分配30B左右的内存.大量的调用会触发GC
替代方案,对于数据结构list:
For(inti=0;i
dosth();
对于其他非线性的数据结构:
var etr = dii.GetEnumerator();
while (etr.MoveNext())dosth(etr.Current);
2、尽量使用stringbuilder替代string.并且new stringbuilder(cap) 尽量预先分配好合适大小的内存.string.format的底层实现是stringbuilder,可以使用。
3、c#对于返回的变量,基本都是新new出来的.尽量少使用.比如
ICollection.ToArray(),会返回一个新new的T[]对象
ToString(),会产生string临时对象
4、不要使用Lambda表达式.每次都会有52B的内存分配. 定义函数替代之
5、很多数据结构 比如list/Dictionary都提供了初始分配cap大小的构造函数.根据实际的需要,优先使用这样的构造函数来初始化数据结构
6、尽量不要使用值类型到System.Object的转换.c#俗称装箱.
类似这样的转换 System.Objectobj = (System.Object)int_i;每次都会有10B的内存分配.这样小内存的频繁调用,很容易触发GC.
如果实在是必须使用,也首先考虑使用类似于MGAME的SimpleGenericParam这样的实现替换之.
7、不要以枚举/自定义struct作为Dictionary的key.尽量考虑用基础类型int等来替代.
他们的GetHashCode都有装箱操作,每次调用TryGetValue查找都会有内存分配.
8、尽量减少new的次数,预分配/成员变量替代临时new变量等.
9、尽可能避免使用LINQ。部分功能无法在某些平台上使用,会分配大量GC Alloc。而且我很讨厌LINQ的一点就是它有可能在某些情况下无法很好的进行AOT编译。比如“OrderBy”会生成内部的泛型类“OrderedEnumerable”。这在AOT编译时是无法进行的,因为它只是在OrderBy的方法中才使用。所以如果你使用了OrderBy,那么在IOS平台上也许会报错。
10、尽可能避免使用delegate以及lambda
二、UNITY引擎篇
1、不要使用Object.name,Unity每次使用都会重新new string返回 ,同理的还有component.name
真需要的话,请缓存再用
2、if(gameobject.tag==”player”)改为gameobject.CompareTag(”player”).前者会额外有内存分配
这里可以总结成所有的UNITY引擎返回的串都要谨慎使用(包括及不限于:gameobject.transform,gameobject.name等)
3、对于在Update中使用的很频繁的类似属性,建议直接保存子对象而不是gameobject。如把gameobject.transform换成直接访问 transform。
4、只读的话,用sharedMaterial替代material
5、控制StartCoroutine的次数
开启一个Coroutine(协程),至少分配37B的内存.Coroutine类的实例 -- 21B
6、谨慎使用GetComponent系列函数
GetComponent函数如果没有找到组件,每次调用大概会分配60KB内存.
对于子结点比较多的GameObject调用GetComponentInChildren,如果调用比较深才找到,额外分配的内存甚至会达到1M多.
要求去除所有的在每帧Update中的GetComponent操作,如果有需要用到请在缓存再用。
7、Shader的property去取不要用名字,用Shader.PropertyToID
8、不要有空的Update/LastUpdate之类的UNITY默认回调函数.
三、其他注意事项
1、TDR协议发送和接收有大量的GC,可以引入对象池解决
2、视口外的逻辑尽量简化.表现性逻辑可以完全不走.可以比较大的提升性能.
3、behaviac的实现,在传递behaviac定义的值参数时,会有装箱GC.写多个重载函数替代泛型实现可解决.
4、AI可根据不同的类型定制不同的更新频率.比如普通小怪就不需要像英雄那样更新频繁.
5、帧同步游戏,可以把逻辑帧的更新分拆,插入到两个逻辑帧中间的渲染帧去做.
6、UGUI的Rebuild很费.可以通过独立更新频繁的Canvas . Canvas. renderMode 修改成 RenderMode.WorldSpace来改善
总的来说,代码层面最终GC的频率需要控制在2分钟以上一次.
1.AddComponent
一个消耗时间8ms左右,非常费。考虑改成依赖注入
2017/7/27
2.String操作
http://www.cnblogs.com/rainbowzc/p/3687193.html
1.使用StringBuilder做字符串连接
String类型是值类型变量,使用 + 操作连接字符串将会导致创建一个新的字符串。如果字符串连接次数不是固定的,例如在一个循环中,则应该使用 StringBuilder 类来做字符串连接工作。因为 StringBuilder 内部有一个 StringBuffer ,连接操作不会每次分配新的字符串空间。只有当连接后的字符串超出 Buffer 大小时,才会申请新的 Buffer 空间。
2.避免ToUpper和ToLower方法
String类型是值类型变量,ToUpper和ToLower会导致创建一个新的字符串,如果频繁调用,则会频繁创建字符串对象。
3.最快的空串比较方法
最快的方法是str.Length == 0
其次是str == String.Empty或str == ""
注:C#在编译时会将程序集中声明的所有字符串常量放到保留池中(intern pool),相同常量不会重复分配。(指的是这个"")
2017/7/30
3.NET内存相关
有时间把这个翻译了
https://www.red-gate.com/products/dotnet-development/ants-memory-profiler/learning-memory-management/memory-management-gotchas
2017/8/26