4

幸福里 C 端 iOS 编译优化实践-优化 40% 耗时

 2 years ago
source link: https://www.51cto.com/article/715377.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者|许斌斌

经过长期的业务迭代,C 端工程增量编译已经严重劣化,2021 年 12 月前,C 端平均增量编译长达 3 分钟以上,严重影响研发效率,急需优化!经过优化之后,增量编译时长降低到 2 分钟左右。

图片

幸福里 app 编译过程

图片

主要耗时分析

  • 全量编译:pod 编译占用大部分时间,多达数百秒,CI 打包需要 20 到 30 分钟。
  • 增量编译:link、资源处理占用大部分耗时(C 端工程优化前该部分占用 130s 耗时)。
图片
图片
图片

LLVM 编译优化

LLVM 编译过程

.m 文件编译从点.o 文件依次经历以下阶段:

图片
  • 预处理:去掉注释、替换宏定义、添加行号和文件标识
  • 词法分析:将代码切成一个个 token
  • 语法分析:验证语法是否正确,生成语义节点
  • 生成 AST:将所有节点组合生成抽象语法树
  • 静态分析:通过语法树进行静态代码检测
  • 生成 LLVM IR:CodeGen 将语法树从顶至下遍历翻译成 IR 代码
  • 生成汇编:将 IR 代码转变成汇编代码
  • 生成目标文件:汇编器将汇编代码转变成机器代码

可以看到,从源文件到目标文件的编译过程中做了大量工作,如果一个源文件新增了一行代码,那么所有研发同学 build 时都要按照这些步骤重新走一遍,增加了大量重复耗时。

dolphin 分布式编译缓存

字节 app infra 团队通过 hook LLVM Clang,对于基本编译命令(比如 oc 文件),可以根据内容、依赖将其哈希成一个唯一的 key,我们编译完新的.m 后,将对应的.o 和 key 存储在本地硬盘和远程服务器上,其他研发同学编译时,就只需要下载.o 文件即可,可以极大提高编译的效率。幸福里 CI 接入 dolphin 后,打包编译部分耗时从 600s 降低到 240s。

图片

主工程 asset 编译

主工程资源在每次编译都会被编译成 Assets.car,项目里有不少图片存放在主工程的资源下,每次编译都会在这一步耗费 30+s,于是将大部分主工程图片资源迁移至 pod 库中去,可以降低主工程资源编译耗时到 5s 内。

copy pods resource

我们工程是用 resources 引用资源,这一步是复制所有 pod 库的资源并编译合并到主工程的 Assets.car,耗时大概在 40s 左右。优化有两个方向:

  1. 如果改成 resource_bundles,那么每个 pod 都享有自己的 bundle 有自己的 Assets.car,不需要每次都编译一遍,增量编译这一步耗时会降低成 0,但是项目改造成本巨大,可当成一个长期目标去做。
  2. 如果我们不需要 care UI 界面,比如做埋点时,就可以写脚本在编译时选择跳过这一步骤,短期可实现。

link 优化

ld64

ld64 工作原理参考:https://mp.weixin.qq.com/s/tSj6JVEg7plJQm7aDHLyMw

静态链接器 ld64 负责分析 compiler 等模块输出的 .o、.a、.dylib、经过对 symbol 的解析、重定向、聚合,组装出 executable。ld64 主要工作流程如下:

图片

zld

zld 是基于 ld64 开发的优化版链接器,增加并发数、使用效率更快的数据结构去优化 link 过程,当然我们也可以参与优化 zld,如飞书一位大佬就通过 map 查找优化线性查找,降低算法时间复杂度优化了符号决议的耗时。

线性查找

图片

map 查找

图片

接入 zld 数据对比

ld64 数据:

图片

zld 数据:

图片

数据对比:

优化前:3.79m

图片

优化后:1.91m

图片

项目 pod install 时会在 pods-target-resources 生成资源拷贝脚本代码, 编译的时候都会运行这个脚本,如果想跳过资源拷贝,直接在 resources 第一行加上 exit 0 即可。

图片

zld 调试

zld 源码:https://github.com/michaeleisel/zld

使用 zld 编译工程,查看编译日志,获取 link 命令代码:

图片

删掉括号和里面的东西,clang 命令后加一个-v,可以显示 link 参数,然后执行脚本,生成 link 参数,复制并删除-demangle 之前的东西,存到 juzi.txt:

-demangle -lto_library /Applications/Xcode.app/Content......

打开 zld 工程,编译模式调整为 release(debug 运行太慢,release 运行快但是不能断点调试),并将 juzi.txt 的参数复制到 arguments,就可以直接调试项目的 link 过程了。

图片
图片
图片

分析 zld 耗时

将 zld 工程跑出来的 release 版可执行文件复制到桌面。

图片

打开 xcode 的 instruments 的 time profiler,选择桌面上的 zld 可执行文件。

图片

将 juzi.txt 参数中的\s-换成 \\n-,并复制到上图的 arguments,然后运行并分析。

-demangle \
-lto_library /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/libLTO.dylib \
-dynamic \
...
图片

如图,getUserVisibleName()耗时较多,我们查看 zld 源码:

图片

经过断点或加日志测试发现,这个方法永远找不到".llvm."的子串(仅作为 demo 测试),于是尝试改成以下代码:

图片

再次编译产生新的可执行文件,经过 instruments 再次测试得到如下数据:

图片
  1. 将 resources 改成 resource_bundles,将资源拷贝耗时真正的降为 0。
  2. 项目中 swift 用的越来越多,可以接 dolphin 对 swift 的编译缓存。
  3. 探索 lld 的行业动态,进一步优化 link 速度。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK