重要:文章路径更新,参考 链接!!!!
摘要
在小游戏每一帧中,JS主线程主要做了两件事情:计算和渲染。相对地,每一帧的Frame Time = 计算耗时 + 渲染耗时。根据Chrome 团队提出的用户感知性能模型 RAIL,播放动画时JS执行时间(即计算耗时)要小于16ms,且应该尽量低于10ms,以留足额外的6ms用以渲染。因此,对于小游戏而言,每一帧的16ms十分重要。
然而,在小游戏中,一些复杂的计算(比如物理引擎模拟)所消耗的时间很难保证会低于10ms,这时往往会造成游戏帧率下降,并出现卡顿的情况。尤其是iOS的JavaScriptCore无法开启JIT,可能造成iOS上小游戏的性能表现很差。
对于计算性能造成游戏帧率下降或卡顿的场景,大多数开发人员会选择异步化执行策略,将执行时间较长的函数(long task)拆分成多个执行时间短的子任务,将子任务分配到每一帧中,按计划顺序执行,直到全部子任务执行完毕。拆分同步逻辑的异步方案虽然对部分场景有效果,但是依旧存在如下问题:
- 不是所有计算逻辑都能够被拆分。比如数组排序, 树的递归查找, 图像处理算法等, 执行中需要维护当前状态, 且调用上非线性, 无法轻易地拆分为子任务。
- 可以拆分的逻辑难以把控力度。拆分的子任务在高性能机器(iphone 11 pro max)上可以控制在 16ms 内, 但在性能落后的机器(iphone6)上表现并不一定理想。 16ms 的用户感知时间, 并不会因为用户手上机器的差别而变化。
- 拆分的子任务并不稳定。计算逻辑可能会随着业务场景发生变化,将同步计算逻辑拆分成子任务,可能会造成每次改动业务都需要review多个子任务的代码。
这个时候,可以使用Worker 的多线程能力, 从宏观上将整个同步 JS 任务异步化。