Mach-O体积优化

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有两种模式:

  • Full LTO是将每个单独的目标文件中的所有LLVM IR代码组合到一个大的module中,然后对其进行优化并像往常一样生成机器代码。
  • Thin 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使用)
  • Deployment Postprocessing的英文原意是:

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

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

  • Strip Debug Symbols During Copy的英文原意是:

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的可执行文件)的符号信息。

  • Strip Linked Product的英文原意是:

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

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

查看App Size报告

  • 方式一: 通过App Store Connect提供准确的App大小;
  • 方式二:通过Xcode内置报告工具,创建App尺寸报告
    a. Archive App
    b. 通过Ad HocDevelopment或者Enterprise等分发方式导出Archive App
    c. 在设置开发分发选项的列表中,选择All compatible device variants以进行应用程序精简,然后启用Rebuild from Bitcode
    d. 签名并且导出。

此过程将创建一个包含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.

  • 方式三:通过脚本的方式指定输出App Size报告:

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

参考链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容