当我们宣布 Mac 应用的 67 版本时,我们非常关注它带来的性能改进。我们的团队在幕后辛勤工作,以简化流程,使整个应用感觉更加流畅和响应更快。请查看 我们的公告帖子 以了解更多信息。
现在,我们想让您了解我们加速符号的方法。这是我们新系列帖子的第一篇,我们将更深入地探讨应用的技术方面。敬请期待更多内容。
我们花了很多时间改进 Sketch 67 的性能,以使 Mac 应用更快。我们已经改进了很多方面,从我们如何使用饱和度渲染背景模糊,到我们如何调整显示更新时间以更接近屏幕刷新率。
我们还努力使使用符号的整体体验更加流畅,有些人问我们是如何做到的。虽然答案不是什么涉及高级计算机科学或数学的疯狂故事,但也并非只是一件事就带来了所有的改变。但我们确实认为,我们可以分享一些有趣的事情,这些事情可能对其他开发人员也适用。
是什么构成了一个符号?
当我们引入符号时,我们从一个基本基础开始。符号由两部分组成;符号母版,就像一个画板,包含多个图层(例如文本图层、组和形状),以及符号实例,它是一个常规图层,只包含对母版的引用。每当我们需要在画布上绘制一个实例时,我们都会找到符号母版,并将其绘制在它的位置。
符号实例还可以包含覆盖,这是一组来自母版中值的派生。文本覆盖是最常见的。在 Sketch 67 之前,如果一个实例有覆盖,我们就会制作母版的完整副本,使用覆盖值更新文本图层,然后绘制该副本。
寻找重点
当我们首次发布符号时,它们只支持文本覆盖。从那时起,我们添加了更多类型的覆盖 - 嵌套符号覆盖、样式覆盖、调整大小规则、智能布局等等。拥有不断壮大的团队来开发这些功能带来了挑战,虽然我们一直努力记录我们的代码并分享知识,但我们并不是一个集体意识。我们有一种挥之不去的感觉,认为我们可以做得更好。
随着功能的演变,它们所构建的基本假设可能不再成立,您在第一天构建的架构可能也不再足够。但与其在出现第一个问题迹象时就重写它,不如将系统再扩展一下通常更有意义。毕竟,我们不可能在每次创建新功能时都重写所有代码。
然而,最终您会遇到一个临界点,我们在开发智能布局时遇到了一个临界点。我们以前将符号实例渲染为符号母版的副本,但必须进行更改才能处理影响嵌套符号的布局调整。现在,我们将符号渲染为分离的组 - 符号实例被转换为一个组,该组包含所有这些图层的副本,递归地深入到所有嵌套符号中。
在发布智能布局之后,我们觉得需要退一步,评估我们的整体方法。不出所料,我们发现了一些不再成立的假设,而且不可避免地,当多个开发人员基于不同的假设进行工作时,现在产生了冲突。例如,我们发现我们有两个缓存用于存储计算后的符号,而且我们花了很多时间提前填充其中一个缓存,而实际上并没有在最需要的地方使用它。
整理符号
我们还决定认真研究我们在应用覆盖、缩放、智能布局等之后如何计算最终渲染的符号实例。正如我之前提到的,这些实例使用分离的组,我们发现我们创建了太多中间副本(深度)对象树。我们设法将它大大减少了。
我之前描述了我们如何制作母版的副本以应用覆盖。虽然这对于大多数符号来说都很好,但对于更复杂和深度嵌套的符号来说,它会呈指数级地恶化。更糟糕的是,我们在模型中使用的双树结构 - 一个可变树,允许 UI 层轻松地进行更改,它由一个无锁不可变模型支持,我们可以安全地在不同的线程之间传递它来执行诸如跨多个线程渲染画布等操作。
简而言之,我们创建了许多中间副本,然后又将它们丢弃。对此没有神奇的答案,这只是一个找出我们从一种树模型到另一种树模型的转换方式,并尝试消除这些转换的过程。使用这种方法,我们可以消除至少六个中间副本,这对于任何尺寸的符号来说都是如此。
这也带来了其他各种连锁反应。例如,这意味着我们可以更好地利用基于身份的缓存。制作一个中间副本会导致缓存未命中,这会导致诸如阴影等相对昂贵的事情重新计算。
更少的工作,速度更快
另一个重大胜利是发现,当符号母版发生更改时,我们绘制的内容太多了。在一个典型的 macOS 应用中,通过尽可能少地绘制来实现快速绘制。这有两个好处
- 通过告诉系统屏幕上已更改的最小区域,我们可以将更少的像素推回屏幕
- 我们不需要重新绘制没有更改的像素,因此您可以跳过遍历图层、绘制路径、阴影等的逻辑。
我们发现,编辑符号母版实际上会导致整个画布重新绘制,这与我们在文档中跟踪更改的方式有关 - 通过计算一系列不可变树上的差异。我们将在以后的帖子中讨论这样做带来的更大的好处,但足以说明这是一个值得修复的问题。
未来的蓝图
如果从中能得到什么教训的话,也许就是定期停止并调查您已经拥有的东西很重要。小效率低下很容易潜入进来,而且因为它们发生在对代码库的其他持续更改之间,所以很容易被忽略。对我们来说,使 Sketch 尽可能地响应和可靠是一个优先事项,这是我们将继续努力的方向。
不过,就目前而言,如果您还没有尝试过 Sketch 67,而且您的文档包含很多符号,我希望您能尝试一下,看看它带来的区别。