1.Avoid retrieving string properties from GameObjects
通常来讲,从C#的object中获取string 属性没有额外的内存开销,但是从Unity中的Gameobject获取string属性不一样,这会产生上一篇讲到的 Native-Managed Bridge(Native内存和托管内存间的传递)
tag和name是GameObject中会产生这种现象的两个属性,我们应该尽量少的在运行时调用这两个属性,比如用在Editor编程中更合适。但是Tag这个东西太常用也非常有用,在运行时经常会用到,这会经常带来麻烦,举个栗子,下边的代码在每次迭代中都会产生额外的内存开销:
for (int i = 0; i < listOfObjects.Count; ++i)
{ if (listOfObjects[i].tag == "Player") { // do something with this object }
}
还好Unity提供了 CompareTag()这个方法来用来tag间的比较,这个方法避免了Native-Managed Bridg(不知道咋避免的,没有讲原理)
来,让我们测试下:
通过键盘我们可以控制使用哪个函数,来瞅瞅结果:
分别运行1000万次,可以看到使用.tag方法时会有很多内存分配,如果分配过多会进而产生GC。并且.tag方法运行大概消耗2000毫秒,而ComparTag方法大概消耗1000毫秒,速度也慢于CompareTag方法。所以结论就是不要使用.tag方法。
很不幸的是Unity对于name属性没有提供不消耗内存的方法,所以有的时候需要调用name属性去判断时,不如考虑考虑用tag来代替。值得一提的是ComparTag方法调用时传入的字符串,比如"Player"并不会产生额外的内存开销,因为在初始化时已经分配好这些硬编码的字符串,这里这是运行时的引用。
2. Use appropriate data structures
C#在System.Collection中提供了很多不同的数据结构,最常用的俩个就是List<T>和Dictionary<K, V>。
如果我们想要做的是遍历数据集,那List更合适一些,因为List在内存中的分布式连续的,因此遍历会更准确更快的命中内存位置。
Dictionary最合适的情况是两个objects相互关联,我们更多的是用来查询,插入,删除等操作。
如果既想要遍历速度快,又想增删改查快,不好意思,没有这种美事儿,目前没有这种数据结构,书作者的建议是。。。如果有这种硬性需求,那就两个一起用,一个list一个dict,比如一个playerList 一个 playerDict
3.Avoid re-parenting Transforms at runtime
在Unity的早期版本中(5.3更早之前)Transform的引用在内存中的排布和Dict很像,也是随机排布。但是新版本后,Unity对于在同一个parent下的所有Transforms,内存排布变成了连续的类似动态数组,这会给例如物理系统和动画系统等许多系统带来非常大的性能提升,因为这加快了Transform的遍历。万事肯定不是完美的,想获得速度的提升,就会有所牺牲。当我们想给一个GameObject 换个爹(parent)时,新的爹(parent)必须从自己已预先分配好的内存空间中分出一部分给这个新移过来的Transform并且重新根据depth排序,如果这个新爹(parent)没有足够的空间去适应新来的儿子(child),那它只好扩展自己的内存buffer,这有可能会消耗不好少时间。
当我们 instantiate 一个新的GameObject时,要处理的一件事就是给这个GameObject认个爹,如果不处理的话默认是null,也就是孤儿,会直接在Hierarchy的root下存放。Hierarchy 根目录下的所有Transform都需要分配一段内存buffer去存储自己现有的儿子们以及未来还可能新有的,但是如果当我们Instantiate后立刻给一个GameObject认个爹,就可以避免分配这部分内存buffer!因此我们可以 在调用GameObject.Instantiate()时传入parent的Transform而避免分配内存这步。
另一种方法是在我们需要之前就给root Transform提前分配更大的内存buffer,从而避免在同一帧里既需要重新给GameObject认爹还需要扩展内存Buffer,这个操作可以通过修改Transform的hierarchyCapacity属性达到,如果我们可以提前就预估好有多少个儿子,那就可以节省很多的内存分配操作。
4.Consider caching Transform changes
Transform脚本存储了它相对于其parent的数据,这意味着如果获取和修改一个Transform脚本的position,rotation或者scale属性,都将引起许多额外的矩阵运算(类似坐标空间变换)。这个object在Hierarchy Window中所处层级越深,那就需要计算的越多。
然而这也意味着如果使用localPosion,localRotation和localScale不会有这部分开销,因为这些值已经是直接存储在了Transform上,可以直接获得,因此能用local属性的时候就尽可能的使用local。
不幸的是世界坐标和本地坐标的变换在很多时候会简化和解决3D的一些问题,因此有时候也有必要牺牲一些小性能来解决复杂的3d数学运算问题。
另外一个要注意的就是要尽量避免不断的对Transform脚本的属性进行更改,因为每次更改都会调用Unity内部的消息机制,Unity需要把更改的消息发送给例如Collider,Rigidbody,Light,Camera等等以保证物理和渲染系统能知道新的Transform属性从而进行对应的更新。
我们会经常看到在同一帧中对Transform的属性进行了多次的修改,这每次修改都会带来内部消息的传递,即使是在同一帧里。因此,我们应该尽可能减少修改次数,比如先用变量缓存,在帧的最后再调用,比如下边这个例子: