Autolayout 性能分析

优化 Autolayout

Florian Kugler 最近发表了一篇关于iOS上 Autolayout 性能的文章. 它研究了 Autolayout 添加视图需要多长时间, 以及所需时间的变化随着视图的数量的增加. 该帖子虽然提供了非常有用的信息, 但似乎并不能最好地代表 Autolayout 的真实表现, 而是展示了一组最坏情况.

在这片报告中,我想更多地了解为什么 Florian 得到了他所做的结果. 我想强调一下 Autolayout 可能会遭遇一些不良的用法, 另外一个我想强调的是,到底是什么让"Autolayout 需要几秒钟才能布局几百个视图"和"Autolayout 可以快速布局几百个视图"这两种说法都是真实的,尽管它们看似矛盾的本性.

测量方法

首先,我想详细说明我如何测量 我认为 Florian 报告中有问题的数字。 我认为这与Florian测量它们的方式略有不同,但更接近地展示了 Autolayout 的真实情况。 我从最初的源项目开始,并进行了修改以测试一些变化。 你可以在GitHub上找到我的项目版本。

为了测量时间,我使用Time Profiler模板在Instruments中运行应用程序。 我不觉得每次都需要重新启动应用程序,因为几乎没有缓存。 我连续3次运行每个测试,清除每个测试之间的视图。 之后,在仪器中,我专注于每个测试运行的样本区域。 为了获得布局所花费的时间,我使用了仪器所说的布局方法所花费的时间。 我计算了3次运行的平均值,并用它来提供这篇文章的数据。

重新创建初步结果

由于我的方法略有不同,而且我使用的是与弗洛里安(第三代iPad)不同的设备,我首先开始测试他所做的相同事情。 他的项目测试了3种布局方式:

  • 视图的平面层次结构,绝对位于根视图中
  • 平面的视图层次结构,彼此相对定位
  • 嵌套的视图层次结构,彼此相对定位

他还通过简单设置 Frame 来完成平面和嵌套层次结构. 下面的图表显示了我为平面层次结构所获得的内容。

如果你比较 Florian 安的报告,你会发现这看起来很不一样。 在 Florian 的图中,绿线比橙线差,但它们都相当接近。 在我的图表中,橙色线条更糟糕(例如,对于600个视图,Florian 得到5秒,而我接近7.5秒,尽管有更快的iPad),但绿线更好(对于 600次观看我得到2.5秒对比弗洛里安的6-7秒)。

我把这个区别归结为测量的差异。 如前所述,我使用了创建约束的方法的时间。 为此,我在每个方法结尾的根视图上调用了-layoutIfNeeded方法。 这会强制Autolayout立即运行,而不是推迟到运行循环结束,这意味着Instruments会计算创建约束的方法的性能,而不是系统方法。

我怀疑Florian正在测量CPU工作的总时间,但这并不一定都归功于Autolayout。 我相信我的方式更能说明Autolayout正在做什么,但Florian’s更能说明应用程序可能无响应的时间。 无论如何,实际值与曲线无关,以及我们可以找到的任何相对改进。

嵌套布局图与 Florian 测试的差异较小,曲线非常相似. 唯一的区别是我的时间稍微快一些,这是在更快的设备上运行时的预期。

Locality 的力量

我注意到在源码中所有的约束都是被添加到根视图上的. 在某些情况下,这是必需的,因为约束引用了根视图。 约束引用的所有视图必须位于要添加到的视图的子树中。 因此,您可以将UI中的每个约束都放入应用程序的根视图中。

出于几个原因,你不想这样做。 最明显的是,在本地添加约束时,理解代码要简单得多。 另一个是它极大地影响了性能。

让我们来看看我们的平面布局。 虽然位置约束需要在根视图上,但大小限制却不是。 我更改了代码,以便将大小约束添加到子视图中,并获得以下结果:

紫色线是相对布局,大小约束尽可能是局部的,红色是绝对布局的等效线。 如您所见,我们正在获得一些性能改进。 我不是百分百肯定,但我有根据的猜测是,这是因为我们正在缩小根视图上的计算大小。 我们让Autolayout执行布局的一部分作为很多小的计算,而不是在一个大blob中计算整个事物。

但这些收益相对较小。 更复杂的计算仍然聚集在一起并且尽可能地本地化。 让我们看一下嵌套布局,因为与视图相关的所有约束都可以放在直接的超视图中,从而大大增加了局部性。 下图显示了这一改进的显着程度。

为了给出实际数字,当将所有约束放在根视图中时,200视图布局花费22.75秒,但是当将它们放在直接超视图上时仅花费2.00秒。 对根视图施加相同的约束会导致代码运行速度慢11倍。 这个教训应该是显而易见的。 使用Autolayout时,尽可能将所有约束放在本地。

更改已存在视图的层级

Florian提到约束满足问题具有多项式复杂性。我们可以在上图的曲线中看到这一点。但是,上述测试在很大程度上无法代表Autolayout的实际使用。了解Autolayout将1000个视图投射到父视图中的速度有多快,这一点非常有用,因为了解NSArray在添加数百万个对象时的速度有多快。但是,创建的大多数NSArray很少能容纳超过100个项目,其中许多项目少于10个。同样,单个视图很少能容纳超过40-50个子视图,或者视图层次结构超过20个 - 30个观点深(我怀疑这些价值被高估)。

更现实的情况是拥有一个视图层次结构,我们想要移动一些视图,或添加一些额外的视图。我基于上面的那些进行了一些测试。同时采用上面使用的尺寸的平面(绝对,非相对)和嵌套布局,我计算了移动所有视图和添加10个额外视图所需的时间。

正如我们从下面的图表中看到的那样,即使在最多1000个视图中,向平面添加额外的10个视图,绝对定位的布局在很大程度上是线性的。这是因为我们只引用根视图,因此不需要重新计算所有其他视图。如果我们将视图插入到相对定位视图链的中间,那么它可能不会那么快。

类似地,移动在很大程度上是线性的,尽管它在1000个视图中确实有峰值。同样,这是因为一个视图的约束不依赖于任何其他同级视图。

如果我们看一下嵌套布局,我们发现移动似乎也是线性的。 虽然它看起来比平面层次更浅,但它只是图表的一个技巧,它们大部分都在同一条线上。 在添加时,我们确实看到了一条曲线,但是我们在这里添加了另外10层视图层次结构,每个层次都依赖于前一个层次。

所有这些都需要注意的是,它们与之前的测试相比有多快。要添加1000个绝对定位的视图需要6.6秒,但要添加额外的10个仅花费0.055秒。这归结为Cassowary Constraint Solver的聪明才智。

它不是每次都试图从头开始重新解决整个问题,而是一个增量系统。它可以重复使用以前的所有计算,只需在添加,编辑或删除约束时修改结果。这就是为什么一次性添加几百个视图可能需要几秒钟,但您可以快速调整该窗口的大小,并重新计算所有约束并设置帧。

Autolayout比手动设置帧慢。它推广了整个UI中解决相当复杂的布局问题。拥有专注于单个视图的专用算法总是会更快运行。 Autolayout的优势不在于在运行时使布局更快,而在于使我们在编码时更快更容易地定义布局。

与我们使用的许多工具一样,Autolayout充分利用了我们拥有丰富处理能力的优势,以便我们更轻松地编写应用程序。对于绝大多数用例而言,如果使用得当,Autolayout的速度已经足够快了。这可能听起来像是一个借口,但它是我们用来证明用更高级别的编程语言而不是汇编语言编写的理由。

参考资料

猜你喜欢

转载自blog.csdn.net/qfeung/article/details/86524591