手淘架构组最新实践 | iOS基于静态库插桩的⼆进制重排启动优化

简介:

image
作者|谢俊逸(极目)
出品|阿里巴巴新零售淘系技术部
本文知识点提炼:
1、APP 启动时 PageFault 的性能分析
2、静态库插桩重排方案的技术原理

背景

近期抖音和Facebook分享了自己通过二进制重排优化启动时间的方案,手淘iOS架构团队也对二进制重排进行了研究,由于手淘工程模块已经二进制化,因此实现了一套基于静态库插桩的重排方案

APP 启动 和 PageFault

当我们向操作系统申请内存时,操作系统并不是直接分配给我们物理内存,而是只标记当前进程拥有该段内存,当真正使用这段段内存时才会分配。这种延迟分配物理内存的方式就通过page fault机制来实现的。当我们访问一个内存地址时,如果该地址非法,或者我们对其没有访问权限,或者该地址对应的物理内存还未分配,cpu都会生成一个page fault,进而执行操作系统的page fault handler。如果是因为还未分配物理内存,操作系统会立即分配物理内存给当前进程,然后重试产生这个page fault的内存访问指令。
image

App在启动时,需要执行各种函数,我们需要读取TEXT段代码到物理内存中,这个过程会发生缺⻚中断,由于启动时所需要执行的代码分布在TEXT段的各个部分,会读取很多⻚面,导致启动时Page Fault 数量非常多。与直接访问物理内存不同,page fault过程大部分是由软件完成的,消耗时间比较久,所以是影响启动性能的一个关键指标。

例如下图中,手淘启动时首先的调用的几个方法 会分布在虚拟内存的各个⻚面中, 执行这些方法时,需要从读取到物理内容中,就会产生多次page fault

如果能将启动阶段需要的读取代码集中排布,将这些方法全都放到相邻的区域中,我们读取这些方法可能就只需要极少的page fault次数。可以减少不必要的page fault时间。达到优化启动时间的效果。

重排前后的函数在页面的布局对比:
image

重排方案

如何获取方法的执行顺序

为了生成order_file, 我们需要确定应用启动时方法的执行顺序。之前抖音和facebook都分享过自己的方案,在实际操作的过程中,我们发现抖音和 facebook 的方案并不适用于手淘。

抖音通过静态扫描和运行时Trace等方法确定 order_file,该方案无法覆盖 initialize、block 和 C++通过寄存器的间接函数调用静态扫描不出来调用。

facebook 分享过通过 llvm 插桩的确定 order_file 的方案,需要使用源码重新打包。由于手淘几乎全是已经编译好的二进制模块,在手淘使用该方案不现实。

只能想其他办法...

手淘之前已经做过pod预编译,我和师兄念纪想到了是否可以通过在汇编层面对pod编译后的静态库进行插桩。在启动时,插桩后的方法都会调用记录方法,从而获得启动方法的执行顺序。在参考了离青对汇编插桩的研究后,确定了静态库插桩的实现方案。

静态库插桩

我们编译过的静态库由.o文件组成,我们可以对.o中的函数代码进行修改,在每个函数的开头插入调用我们指定记录函数的指令。

举个例子:

插入前-[MyApp window]:的汇编代码

-[MyApp window]:
0000000000002d88 adrp x8, #0x
0000000000002d8c ldrsw x8, [x8, #0xf18]
; 0x2f18@PAGEOFF, _OBJC_IVAR_$_MyApp._window
0000000000002d90 ldr x0, [x0, x8]
0000000000002d94 ret

插入后的 汇编代码,可以看到 增加了跳转到_record_method的指令,并且补上了prologue和
epilogue。

-[MyApp window]:
0000000000002ebc stp x29, x30, [sp, #-0x10]!
0000000000002ec0 mov x29, sp
0000000000002ec4 bl _record_method
0000000000002ec8 ldp x29, x30, [sp], #0x
0000000000002ecc adrp x8, #0x
0000000000002ed0 ldrsw x8, [x8, #0xc0]
0000000000002ed4 ldr x0, [x0, x8]
0000000000002ed8 ret

生成order file

linkmap记录了连接过程中的相关信息。其中包含链接用到的symbol相关的信息。通过pc address减去slide得到的地址,我们可以在linkmap中找到对应的symbol.

address = pc - slide. // 因为ASLR, APP 可执行文件随机载入的原因,需要处理一下偏移
量。

我们需要将之前记录的地址转换成对应的符号,为了真实还原线上的执行环境,我们只是在app中简单地的记录了 pc地址 和 Image的偏移量。通过解析linkmap,获取函数的地址区间, 得到距离address最近的symbol,生成order_file。

linkmap 文件:

# Symbols:
# Address Size File Name
0x100001630 0x00000039 [ 2] -[ViewController viewDidLoad]
0x100001670 0x00000092 [ 3] _main
0x100001710 0x00000080 [ 4] -[AppDelegate application:didFinishLaunchingWithOptions:]
0x100001790 0x00000040 [ 4] -[AppDelegate applicationWillResignActive:]
0x1000017D0 0x00000040 [ 4] -[AppDelegate applicationDidEnterBackground:]
0x100001810 0x00000040 [ 4] -[AppDelegate applicationWillEnterForeground:]
0x100001850 0x00000040 [ 4] -[AppDelegate applicationDidBecomeActive:]
0x100001890 0x00000040 [ 4] -[AppDelegate applicationWillTerminate:]

更改符号的排列顺序

默认情况下,ld链接器会按照链接的顺序将各个.o文件的数据重新布局生成可执行文件。ld链接器提供-order-file选项操控数据排列的顺序。在Xcode中可以通过Order File选项指定符号排序文件。

//Order file 内容例子:
+[xxxxx1 load]
+[xxxxx2 swizzleResumeAndSuspendMethodForClass:]
+[xxxxx3 load]
+[xxxxx4 initialize]___
+[xxxxx5 initialize]_block_invoke
+[xxxxx6 initialize]___
+[xxxxx7 initialize]_block_invoke
...

优化效果

通过精准的启动函数重排,最后重排效果还是很可观的,在iPhone6上优化了400ms的启动时间。

参考

感谢抖音团队和Facebook团队提供优化新思路

抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15%https://mp.weixin.qq.com/s/Drmmx5JtjG3UtTFksL6Q8Q
Improving iOS Startup Performance with Binary Layout Optimizations
https://atscaleconference.com/videos/performance-scale-improving-ios-startup-performance-with-binary-
layout-optimizations/
Linux下Page Fault的处理流程 https://cloud.tencent.com/developer/article/1459526

We are hiring

淘宝基础平台团队正在进行社招招聘,岗位有iOS Android客户端开发工程师、Java研发工程师、C/C++研发工程师、前端开发工程师、算法工程师,欢迎投递简历至📮:junzhan.yzw@taobao.com
如果你想更详细了解淘宝基础平台团队,点击下方“阅读原文”观看团队介绍视频
更多淘宝基础平台团队的技术分享,可关注淘系技术微信公众号AlibabaMTT

目录
相关文章
|
10月前
|
数据采集 监控 API
移动端性能监控探索:iOS RUM SDK 技术架构与实践
阿里云 RUM SDK 作为一款性能体验监控采集工具,可以作为辅助 App 运维的强有力助手,提升您的问题排查效率。
507 75
|
10月前
|
存储 运维 分布式计算
零售数据湖的进化之路:滔搏从Lambda架构到阿里云Flink+Paimon统一架构的实战实践
在数字化浪潮席卷全球的今天,传统零售企业面临着前所未有的技术挑战和转型压力。本文整理自 Flink Forward Asia 2025 城市巡回上海站,滔搏技术负责人分享了滔搏从传统 Lambda 架构向阿里云实时计算 Flink 版+Paimon 统一架构转型的完整实战历程。这不仅是一次技术架构的重大升级,更是中国零售企业拥抱实时数据湖仓一体化的典型案例。
659 0
|
11月前
|
数据采集 存储 运维
MyEMS:技术架构深度剖析与用户实践支持体系
MyEMS 是一款开源能源管理系统,采用分层架构设计,涵盖数据采集、传输、处理与应用全流程,支持多协议设备接入与多样化能源场景。系统具备高扩展性与易用性,结合完善的文档、社区、培训与定制服务,助力不同技术背景用户高效实现能源数字化管理,降低使用门槛与运维成本,广泛适用于工业、商业及公共机构等场景。
442 0
|
11月前
|
数据采集 运维 数据可视化
AR 运维系统与 MES、EMA、IoT 系统的融合架构与实践
AR运维系统融合IoT、EMA、MES数据,构建“感知-分析-决策-执行”闭环。通过AR终端实现设备数据可视化,实时呈现温度、工单等信息,提升运维效率与生产可靠性。(238字)
|
10月前
|
存储 SQL 消息中间件
从 ClickHouse 到 StarRocks 存算分离: 携程 UBT 架构升级实践
查询性能实现从秒级到毫秒级的跨越式提升
|
11月前
|
消息中间件 缓存 监控
中间件架构设计与实践:构建高性能分布式系统的核心基石
摘要 本文系统探讨了中间件技术及其在分布式系统中的核心价值。作者首先定义了中间件作为连接系统组件的"神经网络",强调其在数据传输、系统稳定性和扩展性中的关键作用。随后详细分类了中间件体系,包括通信中间件(如RabbitMQ/Kafka)、数据中间件(如Redis/MyCAT)等类型。文章重点剖析了消息中间件的实现机制,通过Spring Boot代码示例展示了消息生产者的完整实现,涵盖消息ID生成、持久化、批量发送及重试机制等关键技术点。最后,作者指出中间件架构设计对系统性能的决定性影响,
|
11月前
|
前端开发 Java 开发者
MVC 架构模式技术详解与实践
本文档旨在全面解析软件工程中经典且至关重要的 MVC(Model-View-Controller) 架构模式。内容将深入探讨 MVC 的核心思想、三大组件的职责与交互关系、其优势与劣势,并重点分析其在现代 Web 开发中的具体实现,特别是以 Spring MVC 框架为例,详解其请求处理流程、核心组件及基本开发实践。通过本文档,读者将能够深刻理解 MVC 的设计哲学,并掌握基于该模式进行 Web 应用开发的能力。
1898 1
|
12月前
|
存储 自然语言处理 前端开发
百亿级知识库解决方案:从零带你构建高并发RAG架构(附实践代码)
本文详解构建高效RAG系统的关键技术,涵盖基础架构、高级查询转换、智能路由、索引优化、噪声控制与端到端评估,助你打造稳定、精准的检索增强生成系统。
2207 2
|
边缘计算 Kubernetes 物联网
Kubernetes 赋能边缘计算:架构解析、挑战突破与实践方案
在物联网和工业互联网快速发展的背景下,边缘计算凭借就近处理数据的优势,成为解决云计算延迟高、带宽成本高的关键技术。而 Kubernetes 凭借统一管理、容器化适配和强大生态扩展性,正逐步成为边缘计算的核心编排平台。本文系统解析 Kubernetes 适配边缘环境的架构分层、核心挑战与新兴解决方案,为企业落地边缘项目提供实践参考。
925 0
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。