*据我所知,iOS版Unity3D基于Mono运行时,而Mono仅具有世代标记和清除GC。
该GC系统无法避免GC时间停止游戏系统。实例池可以减少这种情况,但不能完全减少,因为我们无法控制实例化发生在CLR的基类库中。那些隐藏的小型且频繁的实例最终将增加不确定的GC时间。定期强制使用完整的GC将大大降低性能(实际上,Mono可以强制使用完整的GC吗?)
那么,如何在使用Unity3D时避免GC时间又不会大幅降低性能呢?
*据我所知,iOS版Unity3D基于Mono运行时,而Mono仅具有世代标记和清除GC。
该GC系统无法避免GC时间停止游戏系统。实例池可以减少这种情况,但不能完全减少,因为我们无法控制实例化发生在CLR的基类库中。那些隐藏的小型且频繁的实例最终将增加不确定的GC时间。定期强制使用完整的GC将大大降低性能(实际上,Mono可以强制使用完整的GC吗?)
那么,如何在使用Unity3D时避免GC时间又不会大幅降低性能呢?
Answers:
幸运的是,正如您所指出的,COMPACT Mono版本使用了世代的GC(与Microsoft的WinMo / WinPhone / XBox完全相反,后者仅维护一个固定列表)。
如果您的游戏很简单,GC应该可以很好地处理它,但是您可能需要研究以下几点。
首先,请先确定这实际上是您遇到的问题,然后再尝试修复它。
您应该合并经常创建或具有深层结构的引用类型。每个示例如下:
Bullet
对象。您应该使用a Stack
作为池(与大多数使用a的实现不同Queue
)。这样做的原因是,Stack
如果使用a,则将对象返回到池中,然后其他对象立即将其抓住;如果幸运的话,它更有可能进入活动页面,甚至进入CPU缓存。只是快一点。此外,请始终限制池的大小(如果超出限制,则不理会“ checkins”)。
List
当您真正想要一个新东西时,不要创建Clear()
它。您可以重新使用后端阵列并保存大量的阵列分配和副本。与此尝试类似,创建具有有意义的初始容量的列表(请记住,这不是限制-仅仅是起始容量),它不需要准确,而只是一个估计值。这基本上适用于任何集合类型-除了LinkedList
。
如果在对象之间传递结构,则使用结构(或通常是值类型)几乎不会带来任何好处。例如,在大多数“好”粒子系统中,单个粒子存储在一个巨大的数组中:数组和索引被传递,而不是粒子本身传递。之所以如此有效,是因为当GC需要收集数组时,它可以完全跳过内容(这是原始数组-在此无需执行任何操作)。因此,GC无需查看1万个对象,而只需查看1个数组:巨大的收益!同样,这仅适用于值类型。
在RoyT之后。提供了一些可行和建设性的反馈,我觉得我需要对此进行更多的扩展。仅在处理大量实体(成千上万)时,才应使用此技术。另外,它必须是一个结构,该结构必须没有任何引用类型字段,并且必须位于显式类型的数组中。与他的反馈相反,我们将其放置在一个数组中,该数组很可能是类中的一个字段-意味着它将要堆积在堆上(我们不打算避免堆分配-只是避免GC工作)。我们真正关心的是它是一个连续的内存块,具有很多值,GC可以在O(1)
操作而不是O(n)
操作中简单地查看它们。
您还应该尽可能靠近应用程序启动位置分配这些数组,以减少发生碎片的可能性,或者减少GC尝试移动这些块时的工作量(并考虑使用混合链接列表而不是内置List
类型) )。
这绝对是使用世代GC 射击自己的最佳方法(请参阅:“性能注意事项”)。您应该只把它当你已经创建了一个极端的垃圾量-在那里,可能是一个问题和一个实例是已加载的水平含量刚过-甚至那么你或许应该只收集第一代()希望防止将对象推广到第三代。GC.Collect(0);
当您不再需要对象时(对于受约束的对象更是如此),将字段为空是值得的。原因在于GC的工作原理的详细信息:它仅删除未植根(即被引用)的对象,即使该对象由于当前集合中其他对象被删除而已被取消植根(注意:这取决于GC)使用中的风味-有些实际上可以清除链条)。此外,如果某个对象在集合中幸存下来,则会立即提升到下一代-这意味着在字段中放置的任何对象都会在集合期间得到提升。收集的每一代都成倍增加(并且很少发生)。
请看以下示例:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// G1 Collection
MyNestObject (G2) -> MyFurtherNestedObject (G2)
// G2 Collection
MyFurtherNestedObject (G3)
如果MyFurtherNestedObject
包含一个兆字节的对象,则可以保证GC不会长时间查看它-因为您无意中将其升级到了G3。与此示例进行对比:
MyObject (G1) -> MyNestedObject (G1) -> MyFurtherNestedObject (G1)
// Dispose
MyObject (G1)
MyNestedObject (G1)
MyFurtherNestedObject (G1)
// G1 Collection
Disposer模式可帮助您设置一种可预测的方式来请求对象清除其私有字段。例如:
public class MyClass : IDisposable
{
private MyNestedType _nested;
// A finalizer is only needed IF YOU CONTROL UNMANAGED RESOURCES
// ~MyClass() { }
public void Dispose()
{
_nested = null;
}
}
除非您调用需要它的东西,否则很少有动态实例化会自动在基础库中发生。绝大多数的内存分配和释放都来自您自己的代码,您可以控制它们。
据我了解,您不能完全避免这种情况-您所要做的就是确保在可能的情况下回收和缓冲对象,使用结构代替类,避免使用UnityGUI系统,并且通常避免在此期间在动态内存中创建新对象当性能很重要时。
您还可以在某些时间强制进行垃圾收集,这可能有帮助也可能没有帮助:请参阅此处的一些建议:http : //unity3d.com/support/documentation/Manual/iphone-Optimizing-Scripts.html
GC.Collect()
=现在可以释放内存,但是以后很可能会出现问题。