Yalda

哈库呐玛塔塔

嗨,我是老张,一名 iOS 开发者。


iOS包体积和编译优化

一. 指令集优化

1. ARM处理器指令集

ARM架构过去称作进阶精简指令集机器(Advanced RISC Machine,更早称作:Acorn RISC Machine),是一个32位精简指令集(RISC)处理器架构,ARM处理器非常适用于移动通讯领域,符合其主要设计目标:体积小、低功耗、低成本、高性能。ARM指令集是指计算机ARM操作指令系统。

指令集 对应机型
i386 模拟器32位处理器
x86_64 模拟器64位处理器
armv6 iPhone, iPhone 3G, iPod 1G/2G
armv7 iPhone 3GS, iPhone 4, iPhone 4S, iPod 3G/4G/5G, iPad, iPad 2, iPad 3, iPad Mini
armv7s iPhone 5, iPhone 5c, iPad 4
arm64 iPhone X,iPhone 8(Plus),iPhone 7(Plus),iPhone 6(Plus),iPhone 6s(Plus), iPhone 5s, iPad Air(2), Retina iPad Mini(2,3)
arm64e XS/XS Max/XR/ iPhone 11, iPhone 11 pro,iPhone 11 Pro Max,iPhone SE (2nd generation),iPhone 12 mini,iPhone 12,iPhone 12 Pro,iPhone 12 Pro Max,Phone 13 mini,Phone 13,iPhone 13 Pro,iPhone 13 Pro Max
关于Architectures

architectures:n. 建筑;架构(architecture 的复数)。

指定工程支持的指令集的集合,如果设置多个architecture,则生成的二进制数据包会包含多个指令集代码,体积会变大。

Xcode 14的 Release Notes中提到: Building iOS projects with deployment targets for the armv7, armv7s, and i386 architectures is no longer supported. (92831716)。

不再支持构建 armv7、armv7s 以及 i386 架构的 iOS 项目。

Xcode 14 更新说明文档

2. Excluded Architectures

Build Settings -> Architectures -> Excluded Architectures

结合自身项目支持情况,项目中不需要支持armv7s和armv7。

Excluded Architectures.png

该编译项在 Release 下面增加配置项目
  • Any iOS SDK 设置为armv7 / &armv7s
  • ``Any iOS Simulator SDK,设置为arm64`;

这个选项的意思是Release模式下针对真机armv7armv7s指令集排除,针对模拟器把arm64排除(模拟器不支持arm架构)。

二. Build Active Architecture Only配置

Build Settings -> Architectures -> Build Active Architecture Only

该编译项用于设置是否只编译当前使用的设备(连线设备)对应的arm指令集

App Clang  Optimization Level.png

该编译选项设置为
  • Debug 模式设置为 Yes
  • Release 模式设置为 No

三. Optimization Level

优主要用来在 二进制大小运行时性能 做取舍。

1. OC 编译最佳优化

Xcode -> Build Setting -> Apple Clang - Code Generation -> Optimization Level

Xcode 是使用 Clang 来编译 Objective-C 语言,我们的 IDE-Xcode 提供给我们 6 个等级的编译选项(官方说明),用来设置生成的代码在速度和二进制大小方面的优化程度。

Optimization Level OC.png

优化选项 说明
None[-O0] 【默认在 Debug 模式下开启】使用此设置,编译器的目标是减少编译的成本,并使调试产生预期的结果,不会优化代码,提供更快的编译速度和更多的调试信息,
Fast[-O,O1] 【会优化代码性能并且最小限度影响编译时间,会占用更多的内存】对于大型函数,优化编译需要更多的时间和更多的内存。-O, -O1通过此设置,编译器试图减少代码大小和执行时间,而不执行任何需要大量编译时间的优化。
Faster[-O2] 【开启不依赖空间/时间折衷所有优化选项,会增加编译时间并且提高代码执行效率】编译器执行几乎所有不涉及空间速度权衡的支持优化。在此设置下,编译器不执行循环展开、函数内联或寄存器重命名。与Fast设置相比,该设置增加了编译时间和生成代码的性能。
Fastest[-O3] 【不推荐使用此模式】编译器会开启所有的优化选项来提升代码执行效率。此模式编译器会执行函数内联使得生成的可执行文件会变得更大。
Fastest Smallest[-Os] 【性能和大小平衡较好,默认在 Release 模式下开启】编译器会开启除了会明显增加包大小以外的所有优化选项。默认-Os是性能和大小平衡比较好的
Smallest,Aggressive Size Optimization[-Oz] 【最小的、激进的大小优化】Oz是Xcode 11之后才出现的编译优化选项,核心原理是对重复的连续机器指令外联成函数进行复用,因此开启Oz,能减少二进制的大小,但同时会带来执行效率但额外消耗。
Fastest, Aggressive Optimization[-Ofast] 【不推荐使用此模式】启动 -O3 中的所有优化,可能会开启一些违反语言标准的一些优化选项。

空间/时间折衷

在计算机科学中,时空时内存权衡是通过使用更多的存储空间(或内存)或通过花费很长时间在极小的空间中解决问题或计算的一种方式。大多数计算机有大量的空间,但不是无限的空间。此外,大多数人愿意等待一段时间进行大计算,但不是永远。因此,如果您的问题需要很长时间,但内存不多,时空权衡将允许您使用更多内存并更快地解决问题。或者,如果它可以很快解决,但需要比你更多的内存,你可以试着花更多的时间在有限的内存中解决问题。

在不同的选项对应的编译速度和二进制文件大小变化趋势

Optimization Level不同选项对比.png

Optimization Level默认是-Os-OzXcode 11新增的编译优化选项,该设置通过将重复的代码模式隔离到编译器生成的函数中来实现额外的尺寸节省。

该编译选项设置为
  • Debug 模式设置为[-O0]
  • Release 模式设置为 [-Oz] 或者[-Os]; (根据实际情况 选择更偏性能还是包大小)

2. Swift 编译最佳优化

Xcode -> Build Setting -> Swift Compiler - Code Generation -> Optimization Level

Swift 语言的编译器是 swiftlang,同时也是基于 LLVM 后端的。Xcode 9.3 版本之后 Swift 编译器会提供新的选项来帮助减少 Swift 可执行文件的大小. 我们的 IDE-Xcode 提供给我们 3 个等级的编译选项

Optimization Level Swift.png

优化选项 说明
No optimization[-Onone] 不进行优化,能保证较快的编译速度
Optimize for Speed[-O] 编译器将会对代码的执行效率进行优化,一定程度上会增加包大小
Optimize for Size[-Osize] 编译器会尽可能减少包的大小并且最小限度影响代码的执行效率。根据项目不同,大致可以优化掉 5% - 30% 的代码空间占用。 相比 -0 来说,会损失大概 5% 的运行时性能。

-Osize 根据项目不同,大致可以优化掉 5% - 30% 的代码空间占用。 相比 -0 来说,会损失大概 5% 的运行时性能。 如果你的项目对运行速度不是特别敏感,并且可以接受轻微的性能损失,那么 -Osize 就值得一用。

该编译选项设置为
  • Debug 模式设置为[-Onone]
  • Release 模式设置为 [-Osize]

四. 编译模式 Compilation Mode

Xcode -> Build Setting -> Swift Compiler - Code Generation -> Compilation Mode

编译模式设置,9.3版本之后可以独立设置了。

Single File 和 Whole Module.png

Incremental(增量)仅编译已修改的文件.

优点:

  • 在进行增量编译时,编译器不必重新编译整个项目,而只能重新编译已更改的文件或依赖已更改的文件
  • 编译器每个文件运行一个实例,因此在具有多个内核的计算机上,它可以编译得更快

缺点:

  • 如果正在优化的内容跨越多个文件,则不会执行一些优化
  • 编译器确实必须从其他文件中获取一些信息,因此它可能会重复此工作超过必要的次数(如果6个文件引用另一个文件,则在只需要1个文件时,该文件可能会对它执行6次一些工作)
Whole Module(全量) 不考虑修改而构建项目中所有文件。

优点:

  • 这将执行快速编译器可以执行的最大优化
  • 与单文件优化相比,执行更少的冗余工作

缺点:

  • 这只会使用一个CPU内核来运行代码上所有快速的优化。这意味着多核计算机将无法充分利用编译您的代码
  • 在增量编译中,您的整个模块仍然需要每次都重新编译
该编译选项设置为
  • Debug 模式设置为Incremental
  • Release 模式设置为Whole Module

Xcode 10+的默认设置。

Xcode -> Build Settings -> Apple Clang - Code Generation - Link-Time Optimization

LTO(Link-Time Optimization) 就是对整个程序代码进行的一种优化,是 LLVM 里在链接时进行跨模块间的优化。

Link-Time Optimization.png

支持的链接选项

  • monolithic(adj. 整体的;)

    大型 LTO:这种模式对二进制进行大型的链接时优化,合并所有的可执行代码到一个单元,并且执行更加激进的编译器优化。大型 LTO 的实现是把所有的输入合并到一个模块,并没有考虑时间和内存的问题,而且还阻碍了增量编译的执行。

  • incremental(adj. 增加的,递增的;)

    这个模式可以对二进制执行部分的链接时优化,在编译单元之间进行内联,并行地在每个单元里执行更激进的编译器优化。这个可以允许更快的增量编译,以及使用更少的内存。

  • No 不做优化

苹果在WWDC2016对LTO的介绍如下:

What is Link-Time Optimization (LTO)?

Maximize runtime performance by optimizing at link-time Inline functions across source files Remove dead code Enable powerful whole program optimizations

将一些函数內联化

去除了一些无用代码

对程序有全局的优化作用

苹果官方称他们已经在他们的应用软件中大量使用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

LTO用编译时间来换取运行时性能 优化不是并行进行的 增量构建重复所有的工作

总结来说: 开启LTO主要是对链接过程的一个优化,并且有link cache,使二次编译的速度更快,另一方面它还很有可能减小code size。

该编译选项设置为
  • Debug 模式设置为No
  • Release 模式设置为incremental

六. Asset Catalog Compiler 之 Optimization

Build Settings -> Asset Catalog Compiler - Options -> Optimization

这个选项可以改变actool在构建Assets.car时选取的编码压缩算法,减少包大小。

改变actool(使用内置在Xcode中的compile asset catalog工具)在构建Assets.car时会按照一定策略选取编码算法,对其中的 png 图片重新编码, 从而减少包大小。Assets.xcassets 压缩格式对最终ipa包下assets.car文件大小的影响较大。

Asset Catalog Compiler 之 Optimization.png

可以把对应的信息生成json文件,对比差异

bash

复制代码 1、打一个ipa包,改为zip格式解压,进入Payload文件夹。打开终端执行
 cd /Users/Desktop/pluto/Payload/pluto.app
 
 2、用find命令定位到Assets.car文件
 find . -name 'Assets.car'
 
 3、使用 assetutil 命令导出图片的信息存储到Assets.json文件中
 sudo xcrun --sdk iphoneos assetutil --info ./Assets.car > /tmp/Assets.json
 
 4、打开生成的Assets.json文件
 open /tmp/Assets.json

以项目中这个头像为例

AccountPhotoIcon.png

可以看到压缩算法为: “Compression” : “lzfse” (苹果开源的一种压缩算法,还有其他的算法)。压缩的算法不同,占用空间的大小也不同。

json

复制代码  {
    "AssetType" : "Image",
    "BitsPerComponent" : 8,
    "ColorModel" : "RGB",
    "Colorspace" : "srgb",
    "Compression" : "lzfse",
    "Encoding" : "ARGB",
    "Idiom" : "universal",
    "Name" : "AccountPhotoIcon",
    "NameIdentifier" : 46082,
    "Opaque" : false,
    "PixelHeight" : 96,
    "PixelWidth" : 96,
    "RenditionName" : "AccountPhotoIcon@2x.png",
    "Scale" : 2,
    "SHA1Digest" : "436897BD6B134BBAE8C0ADE4D57C13AC8266307AC9767672E7B50B35274A86F0",
    "SizeOnDisk" : 334,
    "State" : "Normal",
    "Template Mode" : "automatic",
    "Value" : "Off"
  }
该编译选项设置为
  • Debug 模式设置为space
  • Release 模式设置为space

七. Make Strings Read-Only

Xcode -> Build Settings -> Apple Clang - Code Generation - Make Strings Read-Only

复用字符串字面量,顾名思义就是减少生成不必要的量。

 Make Strings Read-Only.png

该编译选项设置为
  • Debug 模式设置为Yes
  • Release 模式设置为Yes

八. Dead Code Stripping

Xcode -> Build Settings -> Linking -> Make Strings Read-Only

C/C++/Swift 等静态语言编译器会在 link 的时候移除未使用的代码,但是对于 Objective-C 等动态语言是无效的。因为 Objective-C 是建立在运行时上面的,底层暴露给编译器的都是 Runtime 源码编译结果,所有的部分都会被判别为有效代码。

Dead Code Stripping.png

该编译选项设置为
  • Debug 模式设置为Yes
  • Release 模式设置为Yes
  • Xcode 默认会开启此选项

九. 优化调试符号

可执行文件中的符号是指程序中的所有的变量、类、函数、枚举、变量和地址映射关系,以及一些在调试的时候使用到的用于定位代码在源码中的位置的调试符号,符号和断点定位以及堆栈符号化有很重要的关系。

1. iOS 的调试符号

iOS 的调试符号是 DWARF 格式的,相关概念如下:

  • Mach-O: 可执行文件,源文件编译链接的结果。包含映射调试信息(对象文件)具体存储位置的 Debug Map。
  • DWARF:一种通用的调试文件格式,支持源码级别的调试,调试信息存在于对象文件中,一般都比较大。Xcode 调试模式下一般都是使用 DWARF 来进行符号化的。
  • dSYM:独立的符号表文件,主要用来做发布产品的崩溃符号化。dSYM 是一个压缩包,里面包含了 DWARF 文件。

Math-O 和 DWARF 和 dsUM.png

使用 Xcode 编译打包的时候会先通过可执行文件的 Debug Map 获取到所有对象文件的位置,然后使用 dsymutil将对象文件中的 DWARF 提取出来生成 dSYM 文件。

2. Strip Style

Xcode -> Build Settings -> Deployment -> Strip Style

表示的是我们需要去除的符号的类型选项

Strip Style.png

  • All Symbols: 去除所有符号,一般是在主工程中开启。
  • Non-Global Symbols: 去除一些非全局的 Symbol(保留全局符号,Debug Symbols 同样会被去除),链接时会被重定向的那些符号不会被去除,此选项是静态库/动态库的建议选项。
  • Debug Symbols: 去除调试符号,去除之后将无法断点调试。

选择不同的Strip Style时,app构建末尾的Strip操作会被带上对应的参数。如果选择debugging symbols的话,函数调用栈中,类名和方法名还是可以看到的。

该编译选项设置为
  • Debug 模式设置为All Symbols
  • Release 模式设置为All Symbols

3. Deployment Postprocessing (这个设置为Yes 有会报错: error build: Command PhaseScriptExecution failed with a nonzero exit code)

Xcode -> Build Settings -> Deployment -> Deployment Postprocessing

Deployment Postprocessing是Strip配置的总开关,只有这个设置为YES之后,下面的Strip Linked Product、Strip Debug Symbols During Copy的设置才会生效。

PS:Deployment Postprocessing这个配置项如果使用xcode打包,xcode会默认把这个变量置为YES, 如果使用脚本打包,记得设置。

该编译选项设置为
  • Debug 模式设置为No
  • Release 模式设置为Yes

4. Strip Linked Product

Xcode -> Build Settings -> Deployment -> Strip Linked Product

并不是所有的符号都是必须的,比如 Debug Map,所以 Xcode 提供给我们 Strip Linked Product 来去除不需要的符号信息(Strip Style 中选择的选项相应的符号),去除了符号信息之后我们就只能使用 dSYM 来进行符号化了,所以需要将 Debug Information Format 修改为 DWARF with dSYM file

Debug Information Format.png

4.1 疑惑没有 DWARF 调试信息之后 Xcode 是靠什么来生成 dSYM 的?

答案其实还是 DWARF,因为 Xcode 编译实际的操作步骤是:

生成带有 DWARF 调试信息的可执行文件 -> 提取可执行文件中的调试信息打包成 dSYM -> 去除符号化信息

去除符号是单独的步骤,使用的是 strip 命令。

4.2 去除符号化信息之后我们只能使用 dSYM 来进行符号化,那我们使用 Xcode 来进行调试的时候会不会太麻烦了?

其实我们完全不用担心这个问题:Strip Linked Product 选项在 Deployment Postprocessing 设置为 Yes 的时候才生效,而在 Archive 的时候 Xcode 总是会把 Deployment Postprocessing 设置为 YES 。

所以我们可以打开 Strip Linked Product 并且把 Deployment Postprocessing 设置为 NO,而不用担心调试的时候会影响断点和符号化,同时打包的时候又会自动去除符号信息。

Strip Linked Product.png

该编译选项设置为
  • Debug 模式设置为No
  • Release 模式设置为Yes
  • 这个选项是默认打开的;

5. Strip Debug Symbols During Copy

Xcode -> Build Settings -> Deployment -> Strip Debug Symbols During Copy

Strip Linked Product 类似,同样也是使用的 strip 命令,将那些拷贝进项目包的三方库、资源或者 Extension 的 Debug Symbol 去除掉。只需要在 Release 模式下开启,否则会影响三方库进行断点调试和符号化。

Strip Debug Symbols During Copy.png

该编译选项设置为
  • Debug 模式设置为No
  • Release 模式设置为Yes

6. Strip Swift Symbols

Xcode -> Build Settings -> Deployment -> Strip Swift Symbols

能帮助我们移除相应 Target 中的所有的 Swift 符号.

Strip Swift Symbols.png

该编译选项设置为
  • Debug 模式设置为Yes
  • Release 模式设置为Yes
  • 该项是默认打开的;

十. Symbols Hidden by Default

Xcode -> Build Setting -> Apple Clang - Code Generation -> Symbols Hidden by Default

用于设置符号默认可见性,XCode会把所有符号都定义为private extern,移除符号信息,包大小会略有减少。动态库设置为NO,否则会有链接错误。

Symbols Hidden by Default.png

该编译选项设置为
  • Debug 模式设置为No
  • Release 模式设置为Yes
  • Framework工程 静态库/动态库,设置为NO,否则会有链接错误。

十一. Debug Information Level

Xcode -> Build Setting -> Apple Clang - Code Generation -> Debug Information Level

切换启用调试符号时发出的调试信息的数量。这可能会影响生成的调试信息的大小,这在大型项目的某些情况下可能很重要(例如使用LTO时)。

  • Compiler default

  • Line tables only

    这种类型的调试信息允许获得带有函数名、文件名和行号的函数调用栈,但是不包含其他数据(比如局部变量和函数参数)。所以当Debug Information Level设置为Line tables only的时候,断点依然会中断,但是无法在调试器中查看局部变量的值.

Debug Information Level.png

该编译选项设置为
  • Debug 模式设置为Compiler default
  • Release 模式设置为Compiler default
  • 这个选项默认就是 Compiler default;

十二. Generate Debug Symbols (不建议)

Xcode -> Build Setting -> Apple Clang - Code Generation -> Generate Debug Symbols

Generate Debug Symbols.png

生成调试符号选项,当这个选项

  • 设置为YES时

    每个源文件在编译成.o文件时,编译参数多了-g和-gmodule,意思是generate complete debug info,所以产生的.o文件会大,从而最终生成的可执行文件也就会变大。

  • 设置为NO时

    在Xcode中不能断点调试。且最后不能生成DSYM文件,即使设置 Debug Information Format设置了,也不能生成。

    因为首先要有调试信息然后才能生成DSYM文件,而设置为NO,意味着不产生调试信息,所以也就没办法生成DSYM文件。

该编译选项设置为
  • 不建议改动,保持设置为 yes

十三. Compress PNG Files

Xcode -> Build Setting -> Compress PNG Files - Packaging -> Compress PNG Files

Compress PNG Files & Remove Text Metadata From PNG Fils.png

当我们在构建过程中,Xcode 会通过自己的压缩算法重新对图片进行处理。通过调研知道 Apple 为了在优化 iPhone 设备读取 png 图片速度,将 png 转换成 CgBI 非标准的 png 格式:

  • extra critical chunk (CgBI)

    额外的关键数据块 CgBI

  • byteswapped (RGBA -> BGRA) pixel data, presumably for high-speed direct blitting to the framebuffer

    字节转换 加快数据交换

  • zlib header, footer, and CRC removed from the IDAT chunk

    移除一些辅助性数据块

  • premultiplied alpha (color’ = color * alpha / 255)

    预乘透明度

在苹果此项的优化下,一般的压缩(有损,无损)处理并不能达到很好的瘦身效果,对于大多数应用来说都是包大小的 负优化

该编译选项设置为
  • 不建议改动,保持设置为 Yes

十四. Remove Text Metadata From PNG Fils

Xcode -> Build Setting -> Compress PNG Files - Packaging -> Remove Text Metadata From PNG Fils

能帮助我们移除 PNG 资源的文本字符,比如图像名称、作者、版权、创作时间、注释等信息。

该编译选项设置为
  • 保持设置为 Yes

十五. 去掉异常支持 (不建议)

Xcode -> Build Setting -> Apple Clang - Language - C++ -> Enable C++ Exceptions

Xcode -> Build Setting -> Apple Clang - Language - C++ -> Enable Objective-C Exceptions

Xcode -> Build Setting -> Apple Clang - Code Generation -> Other C Flags

1. Enable C++ Excptions 和 Enable Objective-C Exceptions 设置为 No

是指项目支持对错误的异常处理。比如try catch、throw之类的;所以如果项目中使用的有类似的异常处理的,这个关闭了之后会报错(Cannot use ‘@try’ with Objective-C exceptions disabled)。

包括宏定义中使用的有try{}、@finally{}之类的,比如@strongify等,如果关闭了最后打包的时候也会报错。

2. Other C Flags添加 -fno-exceptions

-fno-exceptions的意思是禁用异常机制,当项目中有try thorw的时候,就不要设置这个。

转载自移动端小伙伴

最近的文章

GitHub多账号管理

使用需求通常情况下,我们会有两个 github 账号:一个是公司的,另一个是私人的。由于 github 是使用 SSH key 的 fingerprint (对应的公钥id_rsa_pub)来判...…

继续阅读

更早的文章

遵循这条规则—如果你想受欢迎

摘自1922 年《美国杂志》,这句话在今天和当时都同样适用。事实上,考虑到应用程序和服务等事物是如何在互联网上营销和销售而无需亲自见到客户和用户的,它在今天可能更加重要。遵循这条规则——如果你想...…

继续阅读
-->