不改代码,Link-Time Optimization提高iOS代码效率 + 汇编代码原理分析

3,051 阅读4分钟
原文链接: mp.weixin.qq.com

本文是作者在腾讯实习时的文章,分享给大家。

1. Link-Time Optimization 原理介绍

Link-Time Optimization 是  LLVM 编译器的一个特性,用于在 link中间代码时,对全局代码进行优化。这个优化是自动完成的,因此不需要修改现有的代码;这个优化也是高效的,因为可以在全局视角下优化代码。

Xcode 目前使用的是 ld 链接器,链接器自带了  ThinLTO 框架可以进行 Link-Time Optimization。苹果在 WWDC 2016 中,明确提出了这个优化的概念,What’s New in LLVM。并且说在苹果内部已经广泛地使用这个优化方法进行编译。

它的优化主要体现在如下几个方面:(后面的章节有例子,建议结合例子阅读)

  1. 多余代码去除(Dead code elimination):如果一段代码分布在多个文件中,但是从来没有被使用,普通的  -O3 优化方法不能发现跨中间代码文件的多余代码,因此是一个“局部优化”。但是 Link-Time Optimization 技术可以在 link 时发现跨中间代码文件的多余代码;

  2. 跨过程优化(Interprocedural analysis and optimization):这是一个相对广泛的概念。举个例子来说,如果一个 if 方法的某个分支永不可能执行,那么在最后生成的二进制文件中就不应该有这个分支的代码;

  3. 内联优化(Inlining optimization):内联优化形象来说,就是在汇编中不使用 “call func_name ” 语句,直接将外部方法内的语句“复制”到调用者的代码段内。这样做的好处是不用进行调用函数前的压栈、调用函数后的出栈操作,提高运行效率与栈空间利用率。

2. 用例子展现优化效果

使用 Link-Time Optimization 的方法是在 Xcode 的 build settings 选项中开启。

以下的优化均使用 -O3 的优化

2.1 多余代码去除(Dead code elimination)

这个例子主要由3个文件组成:

func1.m(用来定义func1)

func2.m(用来定义func2)

main.m(主函数)

编译后的汇编代码(未启用优化):

在以下代码中,我们可以明显地发现在 main 函数中,进行了  func1 的调用,可是 func1 并没有任何作用,属于需要去除的 “Dead code”。

编译后的汇编代码(启用优化):

启用优化后,我们可以看到,call func1 的代码已经被移除。同时,还对  func1 和 func2 进行了一些其他的优化。

2.2 跨过程和内联优化

这个例子主要由 3 个文件组成

funcs.h:声明了 extern 的函数名

funcs.m:函数的具体实现

main.m:主函数入口

编译后的汇编代码(未启用优化):

从下面的代码中,我们可以看出,func1 和  func4 是分布在不同的函数中的。

编译后的汇编代码(启用优化):

从下面的代码中,我们可以看出,func1 和  func4 是一起被“复制”到了 main 函数中。同时,  push 和 pop 这类的压栈、出栈代码也都消失了,因为并没有出现类似“ call func_name”的函数调用。

2.3 总结

从上面的例子可以看出,开启这个优化后,一方面减少了汇编代码的体积,一方面提高了代码的运行效率。

3. QQ 音乐优化实例

在 QQ 音乐 iOS 版中,开启这项优化将二进制文件的体积从 130 MB 缩小至约  125 MB,缩减了约 4%。运行效率因为受网络等因素的影响,暂时无法具体测量。

从具体的文件来看,

优化前:

优化后:

从代码行数上来看,汇编代码行数几乎保持不变,说明并没有很多 “Dead code”。

4. 进一步拓展

前面在 build settings 中,我们设置了  Monilithic 的优化方式,这种方式并不支持多线程和增量链接。因此,在新的版本中,苹果使用了新的优化方式 Incremental,大大减少了链接的时间。建议开启。

小插曲:在开启 Incremental 的选项后,QQ音乐项目出现了“ duplicate symbols”的错误,经查是之前代码不规范引起的。全局变量在定义时,必须使用 static 关键字或者单独在  .m 文件中定义,.h 文件中只能声明变量,而不应该定义变量。

推荐阅读

剖析 ARM 64 架构中的 objc_msgSend 再谈 __bridge, __bridge_transfer, __bridge_retained