开启Link Time Optimization(LTO)后到底有什么优化?

之前做build settings相关的调研,一直以为开启LTO后主要是能给包大小带来优化(在debug模式下包大小确实减小了5.7M),但是最终打包(release下)发现包大小不减反增了0.2M,于是仔细去查找了一下相关资料。

官方说得很清楚,开启LTO主要有这几点好处

(1)将一些函数內联化
(2)去除了一些无用代码
(3)对程序有全局的优化作用

所以对包大小造成影响的应该是前面两点。

什么是LTO?

image.png

LTO就是build settings中的一个编译选项,正如其名一样,Link Time Optimization,就是在链接的时候对程序进行了一些优化。我们具体来看看到底怎么优化的。

image.png

一个程序的运行过程如图,所有的文件编译成.o文件,然后所有的.o文件和一些需要的framewor再通过链接生成一个.app文件,也就是我们最后的可执行文件。

image.png

在开启LTO(Monolithic)后这些.o文件会附带一些优化信息,让它们在link的时候生成一个单一的整体的.o文件,再和需要的framework链接生成可执行文件。

苹果官方称他们已经在他们的应用软件中大量使用LTO,并且相比常规release模式在运行速度上提升了10%。此外它还会使用PGO(按配置优化)来优化代码,并且还能减小代码体积

Apple uses LTO extensively internally

  • Typically 10% faster than executables from regular Release builds Multiplies
  • with Profile Guided Optimization (PGO)
  • Reduces code size when optimizing for size

这里也带来了很明显的缺点,特别是在有debug info的时候,代码编译耗时和更大的内存占用且二次编译的时候得全部重新编译。

LTO trades compile time for runtime performance

  • Large memory requirements
  • Optimizations are not done in parallel
  • Incremental builds repeat all the work

于是苹果又做了一个优化,就是开启line-tables-only。官方描述如下

Debug Information Level [CLANG_DEBUG_INFORMATION_LEVEL]

Toggles the amount of debug information emitted when debug symbols are enabled. This can impact the size of the generated debug information, which can matter in some cases for large projects (such as when using LTO).

在开启line-tables-only后LTO内存占用提升百分之四十。

开启前
开启后

这就是LTO开启monolithic所进行的优化,所以苹果之前也是建议我们在开启LTO的同时开启line-tables-only。不过这还没完,后来苹果又有了一个新的技术,也就是LTO的incremental。

新的LTO主要又做了下面这几个改进

  • 分析和内联不合并对象文件
  • 提升编译速度
  • 二次编译有链接器缓存

下面看看开启incremental后的build过程:


image.png

这里主要过程是,在生成.o文件后,会产生一个analysis文件由于链接的优化,然后每个.o文件通过优化后生成一个新的.o文件,再与其它framework进行链接。这里通过LTO链接后会有一个link cache,当下次build的时候,如果没有修改,就不需要重新编译,所以二次编译就会很快,只需要编译和链接少数修改过的文件。

去年,苹果再次优化了incremental LTO,让link的时间有了更显著的提升,所以现在即使不开启line-tables-only也是可以开启LTO的了。

linkmap分析

由于在我们的项目中开启LTO后包大小反而增大,感觉这不太符合预期。于是查看了一下linkmap,发现TEXT、DATA、Symbols这些字段在开启LTO后确实都有减小,而Dead Stripped Symbols显示的符号大大的减少了。我猜测是因为我们项目中开启了符号剥离,在没有开启LTO的时候,符号剥离比较完全,而开启LTO后对符号剥离造成了影响,使符号剥离的数量大大减小,从而对包大小也带来了影响。

总结

开启LTO主要是对链接过程的一个优化,并且有link cache,使二次编译的速度更快,另一方面它还很有可能减小code size,在前面的linkmap分析中,确实基本可以保证开启LTO能对代码进行优化,但是由于对符号剥离的影响,具体是否能减小包大小还是得通过打包测试。这里我的建议还是在release模式下开启LTO。由于开启LTO后会对断点的单步执行有影响
image.png

所以debug模式下还是不建议开启。

推荐阅读更多精彩内容