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

简介: 手机淘宝稳稳的底盘,架构组干货

image.png

作者|谢俊逸(极目)
出品|阿里巴巴新零售淘系技术部

本文知识点提炼:
1、APP 启动时 PageFault 的性能分析
2、静态库插桩重排方案的技术原理

背景

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

▐ APP 启动 和 PageFault

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

image.png

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

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

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

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

image.png

重排方案

▐ 如何获取方法的执行顺序

为了生成 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%
[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的处理流程

We are hiring

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

image.png

相关文章
|
2月前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
106 3
|
4月前
|
存储 iOS开发
iOS 开发,如何进行应用的本地化(Localization)?
iOS 开发,如何进行应用的本地化(Localization)?
123 2
|
4月前
|
存储 数据建模 数据库
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
42 0
|
4月前
|
安全 编译器 Swift
IOS开发基础知识: 对比 Swift 和 Objective-C 的优缺点。
IOS开发基础知识: 对比 Swift 和 Objective-C 的优缺点。
106 2
|
2月前
|
API 开发工具 Android开发
iOS 和 Android 平台的开发有哪些主要区别?
iOS与Android开发区别:iOS用Objective-C/Swift,App Store唯一下载渠道;Android用Java/Kotlin,多商店发布(如Google Play、华为市场)。设计上,iOS简洁一致,Android灵活可定制。开发工具,iOS用Xcode,Android用Android Studio。硬件和系统多样性,iOS统一,Android复杂。权限管理、审核流程及API各有特点,开发者需依据目标平台特性进行选择。
36 3
|
10天前
|
前端开发 Android开发 iOS开发
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
【4月更文挑战第30天】Flutter 框架实现跨平台移动应用,通过一致的 UI 渲染(Skia 引擎)、热重载功能和响应式框架提高开发效率和用户体验。然而,Android 和 iOS 的系统差异、渲染机制及编译过程影响性能。性能对比显示,iOS 可能因硬件优化提供更流畅体验,而 Android 更具灵活性和广泛硬件支持。开发者可采用代码、资源优化和特定平台优化策略,利用性能分析工具提升应用性能。
【Flutter前端技术开发专栏】Flutter在Android与iOS上的性能对比
|
11天前
|
存储 Swift iOS开发
使用Swift开发一个简单的iOS应用的详细步骤。
使用Swift开发iOS应用的步骤包括:创建Xcode项目,设计界面(Storyboard或代码),定义数据模型,实现业务逻辑,连接界面和逻辑,处理数据存储(如Core Data),添加网络请求(必要时),调试与测试,根据测试结果优化改进,最后提交至App Store或其它平台发布。
31 0
|
11天前
|
安全 Swift iOS开发
【Swift 开发专栏】Swift 与 UIKit:构建 iOS 应用界面
【4月更文挑战第30天】本文探讨了Swift和UIKit在构建iOS应用界面的关键技术和实践方法。Swift的简洁语法、类型安全和高效编程模型,加上与UIKit的紧密集成,使开发者能便捷地创建用户界面。UIKit提供视图、控制器、布局、动画和事件处理等功能,支持灵活的界面设计。实践中,遵循设计原则,合理组织视图层次,运用布局和动画,以及实现响应式设计,能提升界面质量和用户体验。文章通过登录、列表和详情界面的实际案例展示了Swift与UIKit的结合应用。
|
11天前
|
存储 安全 Swift
【Swift 开发专栏】使用 Swift 开发一个简单的 iOS 应用
【4月更文挑战第30天】本文介绍了使用 Swift 开发简单 iOS 待办事项应用的步骤。首先,阐述了 iOS 开发的吸引力及 Swift 语言的优势。接着,详细说明了应用的需求和设计,包括添加、查看和删除待办事项的功能。开发步骤包括创建项目、界面搭建、数据存储、功能实现,并提供了相关代码示例。最后,强调了实际开发中需注意的细节和优化,旨在帮助初学者掌握 Swift 和 iOS 开发基础。
|
19天前
|
iOS开发 开发者 UED
利用SwiftUI构建动态列表:iOS开发的新范式
【4月更文挑战第22天】在本文中,我们将深入探讨如何使用SwiftUI来创建动态列表。SwiftUI是苹果最新推出的用户界面工具集,它允许开发者以声明式的方式描述用户界面,从而简化了代码的复杂性。我们将通过具体的代码实例,展示如何利用SwiftUI的List和ForEach视图来创建动态列表,并讨论其在实际开发中的应用。
18 2