Mach-O体积优化

2021-03-08  本文已影响0人  differ_iOSER

iOS 开发进阶 文章汇总

目录

Mach-O体积优化

什么是Bitcode?英文原意是:

Bitcode is an intermediate representation of a compiled program. apps you upload to App Store Connect that contain bitcode will be compiled and linked on the App Store. Including bitcode will allow Apple to re-optimize your app binary in the future without the need to submit a new version of your app to the App Store.
For iOS apps, bitcode is the default, but optional. For watchOS and tvOS apps, bitcode is required. If you provide bitcode, all apps and frameworks in the app bundle (all targets in the project) need to include bitcode.

Bitcode是编译后生成汇编之前的中间表现:

底层编译流程:


包含Bitcode并上传到App Store ConnectApps会在App Store上编译和链接。包含Bitcode可以在不提交新版本App的情况下,允许Apple在将来的时候再次优化你的App二进制文件。
Xcode中,默认开启Bitcode。如果你的App要支持BitcodeApp使用到的其他二进制形式也要支持Bitcode

链接时间优化(LTO)

Link Time Optimization (LTO) 链接时间优化是指:

通过整个程序分析和跨模块优化来获得更好的运行时性能的方法。

在编译阶段,clang将发出LLVM bitcode而不是目标文件。

链接器识别这些Bitcode文件,并在链接期间调用LLVM以生成将构成可执行文件的最终对象。

接下来会加载所有输入的Bitcode文件,并将它们合并在一起以生成一个模块。

通俗来讲,链接器将所有目标文件拉到一起,并将它们组合到一个程序中。链接器可以查看整个程序,因此可以进行整个程序的分析和优化。通常,链接器只有在将程序翻译成机器代码后才能看到该程序。

LLVMLTO机制是通过把LLVM IR传递给链接器,从而可以在链接期间执行整个程序分析和优化。所以,LTO的工作方式是编译器输出的目标文件不是常规目标文件:它们是LLVM IR文件,仅通过目标文件的文件扩展名伪装为目标文件。

LTO有两种模式:

进行LTO而不是一次全部编译的优点是(部分)编译与LTO并行进行。对于完整的LTO(-flto=full),仅并行执行语义分析,而优化和机器代码生成则在单个线程中完成。对于ThinLTO(-flto=thin),除全局分析步骤外,所有步骤均并行执行。因此,ThinLTOFullLTO或一次编译快得多。
使用的编译链接参数有:

clang:
-flto=<value> 设置LTO的模式:full或者thin,默认full。
-lto_library <path> 指定执行LTO方式的库所在位置。当执行链接时间优化(LTO)时,链接器将自动去链接libLTO.dylib,或者从指定路径链接。

Xcode Build Setting中的设置为:

通过实例来分析一下:

--- a.h ---
extern int  foo1(void);
extern void foo2(void);
extern void foo4(void);

--- a.c ---
#include "a.h"

static signed int i = 0;

void foo2(void) {
  i = -1;
}

static int foo3() {
  foo4();
  return 10;
}

int foo1(void) {
  int data = 0;

  if (i < 0)
    data = foo3();

  data = data + 42;
  return data;
}

--- main.c ---
#include <stdio.h>
#include "a.h"

void foo4(void) {
  printf("Hi\n");
}

int main() {
  return foo1();
}

进入终端运行:

  1. a.c编译生成bitcode格式文件
clang -flto -c a.c -o a.o
  1. main.c正常编译成目标文件
clang -c main.c -o main.o
  1. 通过LTOa.cmain.c通过LTO方式链接到一起
clang -flto a.o main.o -o main

按照LTO优化方式:

  1. 链接器首先按照顺序读取所有目标文件(此时,是bitcode文件,仅伪装成目标文件)并收集符号信息。
  2. 接下来,链接器使用全局符号表解析符号。找到未定义的符号,替换weak符号等等。
  3. 按照解析的结果,告诉执行LTO的库文件(默认是libLTO.dylib)那些符号是需要的。紧接着,链接器调用优化器和代码生成器,返回通过合并bitcode文件并应用各种优化过程而创建的目标文件。然后,更新内部全局符号表。
  4. 链接器继续运行,直到生成可执行文件。

我们的实例中,LTO整个的优化顺序为:

  1. 首先读取a.obitcode文件)收集符号信息。链接器将foo1()、foo2()、foo4()识别为全局符号。
  2. 读取main.o(真正的目标文件),找到目标文件中使用的符号信息。此时,main.o使用了foo1(),定义了foo4().
  3. 链接器完成了符号解析过程后,发现foo2()未在任何地方使用它将其传递给LTOfoo2()一旦可以删除,意味着发现foo1()里面调用foo3()的判断始终为假,也就是foo3()也没有使用,也可以删除。
  4. 符号处理完毕后,将处理结果传递给优化器和代码生成器,同时,将a.o合并到main.o中。
  5. 修改main.o的符号表信息。继续链接,生成可执行文件。

查看最后生成的可执行文件main的符号表信息:

可以看到,链接完成之后,我们自己声明的函数只剩下:mainfoo1foo4

这个地方有个问题,foo4函数并没有在任何地方使用,为什么没有把它干掉?

因为LTO优化以入口文件需要的符号为准,来向外进行解析优化。所以,要优化掉foo4,那么就需要使用一个新的功能dead strip

dead strip

链接器的-dead_strip参数的作用是:

Remove functions and data that are unreachable by the entry point or exported symbols.

简单来讲,就是移除入口函数或者没有被导出符号使用到的函数或者代码。
现在foo4正是符合这种情况,所以,可以通过-dead_strip来删除掉无用代码。

放大到动态库,在创建动态库时可以使用-mark_dead_strippable_dylib

Specifies that the dylib being built can be dead strip by any client. That is, the dylib has no initialization side effects. So if a client links against the dylib, but never uses any symbol from it, the linker can optimize away the use of the dylib.

指明,如果并没有使用到该动态库的符号信息,那么链接器将会自动优化该动态库。不会因为路径问题崩溃。
同时,你也可以在App中使用-dead_strip_dylibs获得相同的功能。

Code Generation Options

代码生成约定的选项:


  1. None[-O0]不优化:

在这种设置下, 编译器的目标是降低编译消耗,保证调试时输出期望的结果。程序的语句之间是独立的:如果在程序的停在某一行的断点出,我们可以给任何变量赋新值抑或是将程序计数器指向方法中的任何一个语句,并且能得到一个和源码完全一致的运行结果。

  1. Fast[-O1]大函数所需的编译时间和内存消耗都会稍微增加:

在这种设置下,编译器会尝试减小代码文件的大小,减少执行时间,但并不执行需要大量编译时间的优化。在苹果的编译器中,在优化过程中,严格别名,块重排和块间的调度都会被默认禁止掉。此优化级别提供了良好的调试体验,堆栈使用率也提高,并且代码质量优于None[-O0]。

  1. Faster[-O2]编译器执行所有不涉及时间空间交换的所有的支持的优化选项:

是更高的性能优化Fast[-O1]。
在这种设置下,编译器不会进行循环展开、函数内联或寄存器重命名。和Fast[-O1]项相比,此设置会增加编译时间,降低调试体验,并可能导致代码大小增加,但是会提高生成代码的性能。

  1. Fastest[-O3]在开启Fast[-O1]项支持的所有优化项的同时,开启函数内联和寄存器重命名选项:

是更高的性能优化Faster[-O2],指示编译器优化所生成代码的性能,而忽略所生成代码的大小,有可能会导致二进制文件变大。还会降低调试体验。

  1. Fastest, Smallest[-Os]在不显着增加代码大小的情况下尽量提供高性能:

这个设置开启了Fast[-O1]项中的所有不增加代码大小的优化选项,并会进一步的执行可以减小代码大小的优化。增加的代码大小小于Fastest[-O3]。与Fast[-O1]相比,它还会降低调试体验。

  1. Fastest, Aggressive, Optimizations[-Ofast]Fastest, Smallest[-Os]相比该级别还执行其他更激进的优化:

这个设置开启了Fastest[-O3]中的所有优化选项,同时也开启了可能会打破严格编译标准的积极优化,但并不会影响运行良好的代码。该级别会降低调试体验,并可能导致代码大小增加。

  1. Smallest, Aggressive Size Optimizations [-Oz]不使用LTO的情况下减小代码大小:

与-Os相似,指示编译器仅针对代码大小进行优化,而忽略性能优化,这可能会导致代码变慢。



总结:

strip

strip:移除指定符号。在Xcode中默认strip是在Archive的时候才会生效,移除对应符号。

strip -x:除了全局符号都可以移除 (动态库使用)
strip -S:移除调试符号(静态库使用) 
strip:除了间接符号表中使用的符号,其他符号都移除(上架App使用)

If enabled, indicates that binaries should be stripped and file mode, owner, and group information should be set to standard values.

也就是打开后,在编译阶段就会运行strip

Specifies whether binary files that are copied during the build, such as in a Copy Bundle Resources or Copy Files build phase, should be stripped of debugging symbols. It does not cause the linked product of a target to be stripped。

通俗来讲,就是当你的应用在编译阶段copy了某些二进制文件时,打开该选项会脱掉该二进制的调试符号。但是不会脱去链接的最终产物(可执行文件\动态库)的符号信息。要脱去链接的产物(App的可执行文件)的符号信息。

If enabled, the linked product of the build will be stripped of symbols when performing deployment postprocessing.

如果没有打开Deployment Postprocessing,则会在Archive处理链接的最终产物(可执行文件)的符号信息。否则,在链接完成之后就会处理符号信息。

查看App Size报告

此过程将创建一个包含App的文件夹,里面有:
a. 一个Universal IPA,包含多个平台的资源文件和二进制程序;
b. 一个Thinned IPA,指定平台的资源文件和二进制程序。

同时还包含一个App Thinning Size Report.txt,里面详细记录了App的体积占用情况:

App Thinning Size Report for All Variants of ExampleApp

Variant: ExampleApp.ipa
Supported variant descriptors: [device: iPhone11,4, os-version: 12.0], [device: iPhone9,4, os-version: 12.0], [device: iPhone10,3, os-version: 12.0], [device: iPhone11,6, os-version: 12.0], [device: iPhone10,6, os-version: 12.0], [device: iPhone9,2, os-version: 12.0], [device: iPhone10,5, os-version: 12.0], [device: iPhone11,2, os-version: 12.0], and [device: iPhone10,2, os-version: 12.0]

App + On Demand Resources size: 6.7 MB compressed, 18.6 MB uncompressed
App size: 6.7 MB compressed, 18.6 MB uncompressed
On Demand Resources size: Zero KB compressed, Zero KB uncompressed

// Other Variants of Your App.

xcodebuild -exportArchive -archivePath iOSApp.xcarchive -exportPath Release/MyApp -exportOptionsPlist OptionsPlist.plist

参考链接

上一篇下一篇

猜你喜欢

热点阅读