对象复用是用空间换时间的一种典型做法。对于 Unity 来说,虽然引擎内部有 GameObject 的对象池,但那只存在于 Native 层,到了 Managed 层仍有进一步优化的空间。通过 Profiler 可以发现,实例化 GameObject 对 CPU 的消耗相当可观。

Unity 中对象池的三种类型

  1. 普通 C# class 的对象池
  2. GameObject 的对象池
  3. byte[] 的内存池

结论

  1. 普通 C# class 的对象池和 byte[] 内存池,复用后性能提升非常显著,GC 压力也大幅降低。
  2. GameObject 对象池的提升相对有限,大约只有 50%。CPU 消耗中约 40% 来自 Alloc 内部操作及复用控制逻辑,另外 60% 消耗在 GameObject.SetActive 上,后者可以根据实际情况进行定向优化。

C# class 对象池与 byte[] 内存池

这两种对象池的实现相对简单。为了尽量避免泄漏,推荐将 IDisposableusing 结合使用,这样也能方便地支持多线程场景。对于 byte[] 内存池,可以采用分段管理的方式,适当引入一定的冗余,从而尽可能降低分配粒度。

GameObject 对象池

Unity 的大部分组件(如 RigidbodyCollider)只能挂载在 GameObject 上,这意味着无论采用何种客户端框架,都无法绕开与 MonoBehaviour 脚本的交互。由此带来的问题是,StartOnDestroy 等生命周期方法的语义发生了变化,使 GameObject 对象池的设计复杂度远高于普通对象池。为此,需要制定明确的规范来约束 MonoBehaviour 的行为。

除了规范约束之外,还需要做好容错处理,例如在合适的时机执行类似 GC 行为的 Collect 操作,或调用 Resources.UnloadUnusedAssets,定期检测对象池的使用情况。这一点在 GameObject 对象池中尤为重要,是保障健壮性、防止泄漏的关键手段。