Secret of Swift Performance Part 2 - Look under the hood

原文由Kostiantyn Koval发表于Medium,地址为Secret of Swift Performance :Part 2 - Look under the hood

当想要分析一个App的性能时,InstrumentsMeasure绝对是我们最好的朋友。我希望每个人都了解Instruments并至少使用过一次。Instruments提供了许多非常有用的工具,来告诉我们:“我们的App使用了多少内存”,“App有多快”,“有没有内存泄漏”等等。

但作为一个软件攻城狮,我们同样需要知道“为什么…?”,“为什么它发生了?”

在我使用Swift时,我曾经看到一些我当时无法理解但很有意思的东西。“为什么这段代码运行这么快?”为了回答这个问题,我必须查看编译出来的汇编代码。这其实并不难,而且非常有用。接下来就来看看是如何做的。

编译Swift代码

我们只是想编译并分析部分代码,而不是整个工程。为此我们需要做几件事:

  • 创建一个新的Swift文件。
  • 创建一个简单的测试函数func test() { ... // 具体代码 }
  • 将需要编译和分析的代码拷贝到函数体中。
  • 调用函数。

这里我们做的是定义一个测试函数,函数体是我们想要检查和分析的代码。当然,我们需要在顶层调用这个函数。

现在我们需要编译这个文件。可以借助于xcrun(Xcode工具)和swiftc(Swift编译器命令行工具)。

  • 打开终端并通过cd命令切换到Swift文件所在的目录。
  • 运行命令”xcrun swiftc -Onone inputFile.swift -o resultFileName“。如xcrun swiftc -Onone Assembly.swift -o result

这个命令将会编译我们的Swift文件。

我们可以使用xcrun swiftc -help命令来查看xcrun swiftc命令的帮助文档。当前我们最感兴趣的是一些优化选项:

  • -O:编译时优化
  • -Onone:编译时不做任何优化
  • -Ounchecked:编译时优化并移除运行时安全检查

使用-O选项很重要。它就跟编译App时使用Release模式一样。通常用来分析要上传到AppStore上的代码。

作为测试,我们使用-Onone模式,因为它生成的汇编代码非常类似于我们的源代码。也可以分别生成两种模式下的代码来做比较。这样我们可以学习下Swift编译器是如何做优化的。

运行:xcrun swiftc -Onone Assembly.swift -o none

会生成一个可执行文件,可以双击运行它。

image

当编译一个Swift文件时,Swift编译器做了以下几件事:

  • 创建一个带有int main(int arg0, int arg1)函数的控制台应用。这是应用的起点。
  • 创建_top_level_code函数。该函数的函数体是Swift文件的顶层可执行代码。在我们的示例中就是调用了test()函数。

获取汇编代码

有许多办法来获取汇编代码。我建议使用Hopper。可以在这里下载并使用Demo模式。使用Hopper最棒的是它可以显示汇编的伪代码,使用起来比较方便。

让我们来获取汇编代码:

  • 打开Hopper > File > Read Executable to Disassemble,选择可执行文件,点击OK

image

image

image

Hopper概述

image

Hopper的界面类似于Xcode,左侧是导航面板,中间是编辑面板,右侧是帮助和Inspector面板。

左侧面板—在这里可以找到所有函数,串标记和字符串,可以点击它们导航到对应的汇编代码。

编辑区— 显示汇编代码,它类似于XcodeSwift或其它。我们可以使用箭头来导航。

分析代码

首先我们需要找到应用入口,在我们的示例中是_main函数。在左侧导航面板中选择它。下面是_main函数的汇编代码。

image

汇编代码很难分析,不过Hopper可以生成伪代码。使用快捷键”Alt+Enter“或者”Window > Show Pseudo Code of Procedure“。现在可以看到_main函数的伪代码了。

image

这样好多了!!

前4行是提取_main函数的参数,我们对此不感兴趣。然后调用了_top_level_code(),正如前面提到的,这应该就是我们的代码。让我们来看看。关闭伪代码视图,选择_top_level_code函数并显示其伪代码。

image

它只调用了__TF4none4testFT_T_()_函数。

Swift生成的函数有特定的命名规范。即模块名+函数名+字符数+参数类型+其它东西。Mike Ash详细介绍了这一规范。

这里可以看到的是none(文件名), test(函数名)。基于这一点,我们可以说它就是test()函数。让我们来检查一下。查找__TF4none4testFT_T_并显示其伪代码。

image

它有3个变量,是16进制格式的,转换一下:

1
2
3
var_8 = 10,
var_10 = 10,
var_18 = 20

这和我们的源代码非常相似,但源代码有一个相加操作,Swift在编译期直接计算出结果了。

1
2
3
var x = 10
var y = 10
var c = x + y

现在你已经知道了:编译Swift代码,反汇编及分析汇编代码。深入后,你可以学习并发现许多有趣的东西。作为比较,我们现在使用-O模式来编译下代码,以看看Swift编译器是如何优化代码的。

运行xcrun swiftc -O Assembly.swift -o optimized命令。

image

正如你所见的,在主函数中没有调用任何函数。没有_top_level_code。没有调用test()函数。

Swift编译器检测到test函数的结果没有被使用,所以将其忽略。而_top_level_code也只调用了一个test()函数,所以也被忽略了。结果是我们获得了一个空的主函数。

这篇文章描述了如何使用工具来分析代码。我发现了许多用这些工具优化Swift的方法,这些方法非常有意思。我将在第三部分中与你们分享,敬请期待……

注:强烈建议手动操作一下,看看自己得到的结果是什么。