UniRx —— MicroCoroutine(微协程)

参考:

UniRx 官方 README

Unity3d 10000 Update() calls

Unity3d 10000 Update() calls 性能优化

总结

微协程是内存高效、快速的协程 worker,是基于 Unity3d 10000 Update() calls 这篇文章阐述的思想实现的

使用 MicroCoroutine 来替换一般的 Coroutine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int counter;

IEnumerator Worker()
{
while(true)
{
counter++;
yield return null;
}
}

void Start()
{
for(var i = 0; i < 10000; i++)
{
// fast, memory efficient
MainThreadDispatcher.StartUpdateMicroCoroutine(Worker());

// fast, memory efficient
Observable.FromMicroCoroutine(Worker).Subscribe(_ => { });

// slow...
// StartCoroutine(Worker());
}
}

MicroCoroutine 的局限性

  • 只支持 yield return null

  • 更新时机由启动方法(StartUpdateMicroCoroutine()StartFixedUpdateMicroCoroutine()StartEndOfFrameMicroCoroutine())决定

进阶

Unity3d 10000 Update() calls 这篇文章主要阐述的思想(建议直接看原文或译文):

开展讨论的例子

MonoBehaviour 中可以定义一些特定的魔术方法,如:Update() 等,这些方法通常有以下问题:

  • 不清楚方法是如何被调用的
  • 不清楚多个 MonoBehaviour 之间,这些方法的调用顺序
  • IDE 友好

有开发者通过扩展 MonoBehaviour 抽象出一个新的统一的 BaseMonoBehaviour,来减少上述的不足:

1
2
3
4
5
6
7
8
9
public abstract class BaseMonoBehaviour : MonoBehaviour {
protected virtual void Awake() {}
protected virtual void Start() {}
protected virtual void OnEnable() {}
protected virtual void OnDisable() {}
protected virtual void Update() {}
protected virtual void LateUpdate() {}
protected virtual void FixedUpdate() {}
}

但这种做法会带来新的问题:所有继承 BaseMonoBehaviour 的脚本,上述声明的魔术方法都会在特定时机被执行,比如在每帧执行 Update() 方法,即使它是一个空方法

性能分析调用 10000 个像 Update() 这样的方法

具体测试流程略可看原文(PS:原文列出的代码比较难理顺逻辑,推荐结合完整的项目代码分析)

原文主要做了 3 种操作的耗时对比:

  • Unity 内部迭代 10000 个 MonoBehaviourUpdate() 方法

  • 列表类型循环迭代 10000 个 ManagedUpdateBehaviorUpdateMe() 方法

  • 数组类型循环迭代 10000 个 ManagedUpdateBehaviorUpdateMe() 方法

总结

  • Unity 在调用像 Update() 这样的魔法方法时,为了防止游戏出错或崩溃,通常都需要做很多的检测和处理,因此当有大量的 MonoBehaviour 时,这些处理累计的耗时就不能轻易忽视了

  • 删除不必要的魔术方法是很有意义的,而上述抽象出 BaseMonoBehaviour 这种做法是不可取的

  • 在开发过程中,当考虑到有大量的对象需要在每帧都执行一些操作时,可以参考使用如原文测试例子的 Manager 模式,采用数组结构循环迭代调用对象的更新操作,减少耗时

  • MicroCoroutine 就是基于此思想,对一般的 Coroutine 函数以数组结构进行组织,来实现内存高效、快速的协程调用,但同时也有一定的限制(见上文)