百度APP iOS端包体积50M优化实践(七)编译器优化

百度APP iOS端包体积50M优化实践(七)编译器优化_第1张图片

一. 前言

百度APP iOS端包体积优化系列文章的前六篇重点介绍了包体积优化整体方案、图片优化、资源优化、代码优化、无用类优化、HEIC图片优化实践和无用方法清理,图片优化是从无用图片、Asset Catalog和HEIC格式三个角度做深度优化;资源优化包括大资源优化、无用配置文件和重复资源优化,代码优化包括无用类优化、无用模块瘦身、无用方法瘦身、精简重复代码、工具类瘦身和AB实验固化。本文重点介绍编译器优化,在百度APP实践中,编译器优化包括GCC语言编译优化、Swift编译优化、LTO优化、剥离调试符号、剥离符号表、剔除未引用的代码、Asset 优化、C++虚函数优化和三方SDK编译器方向瘦身。此外,我们重点介绍了指令集架构优化、XCode升级优化和Swift内置动态库优化,这三个模块优化的基础原理都涉及到编译器,所以我们在此篇章一起介绍。

百度APP iOS端包体积优化实践系列文章回顾:

1、《百度APP iOS端包体积50M优化实践(一)总览》

2、《百度APP iOS端包体积50M优化实践(二) 图片优化》

3、《百度APP iOS端包体积50M优化实践(三) 资源优化》

4、《百度APP iOS端包体积50M优化实践(四)代码优化》

5、《百度APP iOS端包体积50M优化实践(五)无用类优化和HEIC图片优化实践》

6、《百度APP iOS端包体积50M优化实践(六)无用方法清理》

二. 编译器优化

2.1 方案综述

百度APP iOS端包体积50M优化实践(七)编译器优化_第2张图片

2.2 GCC语言编译优化

2.2.1 综述

通过GCC编译优化,产生体积更小的二进制产物,对OC、C、C++都有效果。

2.2.2 Objective C++编译优化

对于Objective C++采用XCode编辑和编译,编译优化配置路径为:Build Settings -> Apple Clang - Code Generation -> Optimize Level,可选参数如下:

百度APP iOS端包体积50M优化实践(七)编译器优化_第3张图片

百度APP iOS端包体积50M优化实践(七)编译器优化_第4张图片

Xcode的默认优化等级是-Os,但我们使用了-Oz优化方式。在WWDC 2019的《What’s New in Clang and LLVM》中,链接地址:https://developer.apple.com/videos/play/wwdc2019/409/ ,详细介绍了这种优化的原理。它通过识别编译单元中的跨函数相同代码序列来减少代码大小。重复的连续机器指令被外联成函数,原始代码序列被替换为外联函数实现相同机器代码的瘦身,但会增加函数调用栈的深度,因此对性能有一定影响。随着时间的推移,iPhone设备的硬件配置越来越高,这种性能损失是可以承受的。下面是一个官方的demo示例来说明-Oz优化的原理。hasse函数和kakutani函数有相同的机器指令,-Oz优化会生成OUTLINED_FUNCTION_O函数,hasse和kakutani指向该函数,从而降低包体积。

百度APP iOS端包体积50M优化实践(七)编译器优化_第5张图片

百度APP iOS端包体积50M优化实践(七)编译器优化_第6张图片

官方给出的收益是25%,从实践效果来看,编译优化参数 -Oz对Objective C++编写的代码有10%的体积收益,对C&C++有30%的体积收益。

2.2.3 C&C++编译优化

在iOS端,许多底层模块都是使用C和C++实现的,例如网络库、播放内核、视觉处理和端智能等。同时,这些模块也支持Android和iOS等多个平台。为了实现跨平台,这些模块通常采用Cmake和GN这两种编译工具。Cmake是一种常见的跨平台编译工具,其主要工作方式是通过读取CMakeLists.txt文件中的指令来生成相应的项目文件。而GN编译工具则是Generate Ninja的缩写,是一种替代Cmake的编译工具。它由Google开源,使用C++编写,主要实现交叉编译,并且可以指定输出平台目标。

无论是使用CMake还是GN,编译器的优化配置都是一样的。对于C++语言,cppFlags选项设置为’-Oz’,而对于C语言,cFlags选项设置为"-Oz"。

2.3 Swift编译优化

Swift编译优化有两个参数 Optimization Level 和Compliation Mode配合使用,配置路径为:Build Settings -> Swift Compiler - Code Generation。

百度APP iOS端包体积50M优化实践(七)编译器优化_第7张图片

Optimization Level可选参数值如下所示:

百度APP iOS端包体积50M优化实践(七)编译器优化_第8张图片

Optimize for Size的核心原理与前面介绍的GCC语言编译优化中的-Oz优化原理相同,都是通过对重复的连续机器指令进行外联并复用,从而降低编译产物的大小。不过,这种优化方式也会对性能产生一定影响,但在当前的硬件设备条件下,这种影响可以忽略不计。

Compliation Mode可选参数值如下所示:

百度APP iOS端包体积50M优化实践(七)编译器优化_第9张图片

Optimize for Size[-Osize]和 Whole Module 同时开启会发挥最佳效果,从实践中可以看到它会减少10%的swift包体积大小。

2.4 LTO优化

LTO,即Link Time Optimization,是苹果官方提出的一种优化策略。根据官方解释,LTO是对整个程序代码进行的一种优化方式,是在LLVM编译器中在链接阶段进行跨模块间的优化。通过这种优化,编译器可以将部分函数内联化,去除未被调用的冗余代码,并进行整体优化,从而使程序运行得更快,这些优化措施可以有效降低程序的代码大小和提高程序执行效率。

配置路径为:Build Settings -> Apple Clang - Code Generation -> Link-Time Optimization,设置值为Incremental,需要在主工程以及要优化的Framework都开启。

百度APP iOS端包体积50M优化实践(七)编译器优化_第10张图片

LTO的优化效果体现在以下三个方面:

1、函数内联化:

LTO可以将一些函数内联化,即在编译时将函数调用的代码直接嵌入到调用点,以减少函数调用的开销。这可以提高程序的执行效率。

2、去除无用代码:

LTO可以识别并去除程序中无用的代码,例如未使用的变量、函数和类等。这可以减少生成的二进制文件的大小,从而提高程序的加载速度和运行效率。

3、全局优化作用:

LTO对程序进行全局优化,可以识别并优化程序中不可能执行的代码分支。例如,如果一个if语句的某个分支永远不会被执行,LTO会将其从生成的二进制文件中移除,这可以提高程序的执行效率和代码质量。

LTO的负面影响包括:

1、降低Link Map的可读性:

Link Map是链接器生成的一种文件,它描述了目标文件之间的链接关系。在使用LTO时,由于进行了全局优化,生成的Link Map中的类名可能会以数字开头,如0.arm64.thinlto.o,这使得Link Map的可读性明显降低。如果需要阅读Link Map,需要先关闭LTO。

2、增加编译和链接时间:

开启LTO会导致编译和链接过程变得更加耗时。这是因为在链接阶段,LTO会进行大量的全局优化,这需要更多的计算资源和时间。对于线上打包或线下编译,这会导致更长的耗时。

2.5 剥离调试符号

图片

Symbols Hidden by Default用于设置符号默认可见性,如果设置为YES,XCode会把所有符号都定义为”private extern”,包大小会略有减少。动态库设置为NO,否则会有链接错误。

2.6 剥离符号表

配置路径为:Build Settings -> Strip Linked Product,选择属性值为YES。

图片

Strip Linked Product 来去除不需要的符号信息 ,去除了符号信息之后我们只能使用 dSYM 文件进行符号化,因此需要将 “Debug Information Format” 修改为 “DWARF with dSYM file”。

百度APP iOS端包体积50M优化实践(七)编译器优化_第11张图片

Strip Debug Symbols During Copy与 Strip Linked Product 原理类似,主要是去除拷贝到项目中的第三方库的符号表。只需在Release 模式下设置为“YES”,而调试模式下仍为“NO”,否则无法对第三方库进行带有符号化的断点调试。

百度APP iOS端包体积50M优化实践(七)编译器优化_第12张图片

2.7 剔除未引用的代码

配置路径为:Build Settings -> Dead Code Stripping,选择属性值为YES。

百度APP iOS端包体积50M优化实践(七)编译器优化_第13张图片

该优化主要是在链接时将 C、C++、Swift 等静态语言无用代码从安装包剔除,但在处理Objective-C时无效,因为Objective-C 是动态语言,基于Runtime机制编译,静态编译判定为无用代码可能在运行时使用。

2.8 Asset优化

Asset编译优化配置路径为:Build Settings -> Asset Catalog Compiler -> Optimization。

百度APP iOS端包体积50M优化实践(七)编译器优化_第14张图片

Optimization可选参数值如下所示:

百度APP iOS端包体积50M优化实践(七)编译器优化_第15张图片

选择Space可以从一定程度优化包大小,收益较小。

2.9 C++减少虚函数的使用

减少虚函数的使用实际上可以减少虚函数表所占用的空间,从而减小程序包的大小。虚函数表是一种用于实现动态绑定的数据结构,其中存储了指向一个类的虚函数的指针。因此,减少虚函数的使用可以减少这些指针的数量,从而减小虚函数表的大小,最终减小程序包的大小。

2.10 三方SDK编译器瘦身

上面已经详细介绍了编译器的配置方式及其优化原理,但仅仅修改主工程的优化设置是不足以实现最佳效果的。为了达到最佳优化效果,每个框架(Framework)都必须按照上述配置进行相应的调整。这意味着在每个框架的构建配置中都需要启用优化,并将编译器参数设置为适当的值,以实现所需的具体优化效果。同时,还需要确保每个框架使用的库和依赖项也已正确配置,以确保它们能够与编译器优化一起正常工作。总之,为了使编译器优化真正发挥作用,需要对每个框架进行必要的配置和微调。

百度APP作为一款旗舰级应用,内部集成了众多第三方SDK,例如百度地图、百度网盘、度小满等。因此,需要推动这些第三方SDK业务方对其编译器进行优化以实现应用瘦身。这些优化可以包括但不限于图片优化、资源优化、代码优化等。通过这些优化措施,可以有效地减小应用的大小和提升其性能,使用户获得更好的使用体验。

三. 指令集架构优化

3.1 iPhone常用指令集架构

iPhone手机采用的都是低功耗的arm处理器,arm指令集架构分为armv6, armv7, armv7s arm64四种类型,保持向下兼容,如设备iphone13支持arm64,但是对于armv7也是支持的,但是armv7无法发挥iPhone13设备的更好的硬件属性。模拟器无法运行arm的指令集,运行的是x86指令集,32位处理器支持的是I386指令集,64位模拟器支持的是x86_64架构,不同设备支持的指令集架构如下所示。

百度APP iOS端包体积50M优化实践(七)编译器优化_第16张图片

随着硬件设备的不断更新,早期设备(如 iPhone4、iPhone5 和 iPad)的市场占有率已经变得微不足道。因此,对于移动设备,我们只需要支持 arm64 架构即可。同理,对于模拟器,我们只需要支持 x86_64 架构,从包体积的优化的角度来看,目前我们的每个库只需要支持arm64和 x86_64 架构,其他架构没必要支持。

优化指令集架构可以减小上传到AppStore的包体积,但对用户下载的包大小没有优化效果。这是因为苹果的App Thinning机制根据不同设备型号的硬件架构生成不同的编译产物,因此不同设备的用户从AppStore下载的包也会有所不同。

3.2 指令集架构设置

  • Architectures选项,Build Settings -> Architectures,值为Standard architectures - $(ARCHS_STANDARD),在真机的编译下实质是(armv7和arm64)在模拟器的时候是(x86_64,i386,arm64)

  • Build Active Architectures Only选项,Build Settings -> Build Active Architectures Only,当其值为Yes时,表示只编译当前一个架构,真机的话一般是arm64, 模拟器是x86_64, 如果为No的时,那就是同时编译第一支持的架构;

  • Excluded Architectures选项,Build Settings -> Excluded Architectures,其值是要排除的架构,例如,如果将其设为arm64,表示产物里面没有arm64架构;

百度APP iOS端包体积50M优化实践(七)编译器优化_第17张图片

3.3 去除无用架构

通过 lipo 命令从老的framework中的mach-o文件拆分出指定架构二进制文件,然后合并,最后用合并后的二进制文件替换老的framework的mach-o文件。

  • 用lipo -info命令查看framework包含的指令集架构信息,如下所示,AbcSDK.framework支持的指令集是x86_64、i386、arm64和armv7;
 lipo -info AbcSDK.framework/AbcSDK Architectures in the fat file: AbcSDK.framework/AbcSDK are: x86_64 i386 arm64 armv7

  • lipo命令抽取指定架构,如下所示,从AbcSDK.framework抽取出arm64架构,放在AbcArm64,抽取出x86_64架构,放在AbcArmX86_64。
lipo AbcSDK.framework/AbcSDK -thin arm64 -output AbcArm64lipo AbcSDK.framework/AbcSDK -thin x86_64 -output AbcArmX86_64

验证 AbcArm64和 AbcArmX86_64架构信息

lipo -info AbcArm64Non-fat file: AbcArm64 is architecture: arm64lipo -info AbcArmX86_64Non-fat file: AbcArmX86_64 is architecture: x86_64

  • lipo命令合并架构,合并AbcArm64和AbcArmX86_64,生成新的newAbc,按预期newAbc有两个架构x86_64和arm64,用lipo -info命令验证。
// 合成x86_64和arm64lipo -create AbcArm64 AbcArmX86_64 -output newAbclipo -info newAbcArchitectures in the fat file: newAbc are: x86_64 arm64
  • 替换原先的二进制文件,经过上面这些操作将有x86_64、i386、arm64和armv7四种指令集架构的AbcSDK.framework,瘦身变成了只支持x86_64和arm64两种指令集的组件。
mv -f newAbc AbcSDK.framework/AbcSDK

四. XCode升级优化

苹果公司一直致力于提高开发者的生产力,每年都会推出新版本的XCode,并对其进行大量的优化。在包体积方面,他们也采取了积极的措施。例如,在22年10月发布的Xcode 14,不仅具备全新的增强功能,更拥有更强大的并行编译能力,能够显著提高项目构建速度。同时,对包体积的优化也相当明显。

为了寻找Xcode 14优化包体积的具体技术点,看了很多WWCD资料,终于在官方文档《improve app size and runtime performance》找到答案,链接地址为https://developer.apple.com/videos/play/wwdc2022/110363/#,Xcode 14从以下三个方面进行了优化包体积:

  • Meesage send 函数调用占用从 12 bytes 降低到 8 bytes;

  • Retain and release 函数调用占用从 8 字节降低到 4 字节;

  • autorelease优化,移除自动释放省略中的 mov 指令,体积降低 4 bytes;

五. Swift内置动态库优化

自2014年WWDC发布以来,在苹果公司的强力推动下,Swift语言取得了显著的发展。其优点代表着iOS开发的发展趋势,随着使用率的不断提升,Swift有望最终取代Objective-C成为iOS开发的首选语言。目前,Swift已经成为各大公司和应用程序的必备开发语言,国内日活跃用户排名前二十的APP中,除了拼多多以外,其他公司都已经采用了Swift进行开发。

然而,在只要采用Swift语言开始开发,就会发现iPA包中新增了Swift系统库。这是因为对于低于iOS12.2的系统,没有内置的Swift系统库,因此XCode在打包生成iPA包时会一并包含Swift库,在iPA包的Frameworks动态库目录发现如下Swift系统库。

百度APP iOS端包体积50M优化实践(七)编译器优化_第18张图片

更进一步来说,如果APP自带的WatchApp也使用了Swift语言,那么在Watch的动态库中还会有一份Swift系统库,这样iPA包中就会包含两份内置库。

优化方法非常简单,只需将APP支持的最低版本修改为12.2即可。因为12.2及以上的系统自带Swift系统库,不需要在APP内置。在百度APP包体积优化实践中发现,优化后iPA包体积减少30M+,30M动态库已经在ipa包不存在,提交AppStore后,从connect后台看数据,有如下收益:

  • iPhoneX 及以下的机型,如iPhoneX、iPhone8、iPhone7,安装包体积减少20M,下载包大小减少10M;

  • iPhoneX以上的机型,如iPhone11、iPhone12,iPhone13没收益,苹果自身做了优化,我们再做这个优化,价值不会体现;

对于百度APP来说,iPhoneX及以下机型的占比不到5%,但是提高APP支持的最低版本号会导致部分用户流失。综合考虑这两个因素,决定不采用Swift内置动态库的优化方案。

六. 总结

相比于代码优化、资源优化和图片优化,编译器优化在包体积优化中的投资回报率(ROI)是最高的。然而,编译器优化的影响范围也是最大的,因为每个库的编译器配置修改会影响该库的所有代码。因此,必须对优化质量进行严格控制。在百度APP的优化实践过程中,编译器方向的优化成功减少了30M的包体积,实现了自身库的全部收益,此外,按照体积排名的前15个三方SDK也全部实现了此收益。

本文系统介绍了百度APP的编译器优化方案,包括GCC语言编译优化、Swift编译优化、LTO优化、剥离调试符号、剥离符号表、剔除未引用的代码、Asset优化、C++虚函数优化和三方SDK编译器方向瘦身等多种手段。此外,还介绍了指令集架构优化、XCode升级优化和Swift内置动态库优化等其他优化方案。后续我们会针对其他优化详细介绍其原理与实现,敬请期待。

——END——

参考资料:

[1]gcc编译器配置:https://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html

[2]LTO使用方法:https://llvm.org/docs/LinkTimeOptimization.html

[3]XCode :https://developer.apple.com/library/archive/documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW102

[4]What’s New in Clang and LLVM:https://developer.apple.com/videos/play/wwdc2019/409/

[5]XCode14介绍:https://developer.apple.com/documentation/xcode-release-notes/xcode-14-release-notes

[6]improve app size and runtime performance:https://developer.apple.com/videos/play/wwdc2022/110363/#

推荐阅读:

百度搜索内容HTAP表格存储系统

大模型时代,“人人可AI”的百度开发者平台长什么样?

数十万QPS,百度热点大事件搜索的稳定性保障实践

百度搜索万亿规模特征计算系统实践

通过Python脚本支持OC代码重构实践(三):数据项使用模块接入数据通路的适配

你可能感兴趣的:(ios,百度APP)