手淘架构组最新实践 | 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天前
|
运维 Cloud Native 持续交付
云原生架构的演进与实践####
【10月更文挑战第16天】 云原生,这一概念自提出以来,便以其独特的魅力和无限的可能性,引领着现代软件开发与部署的新浪潮。本文旨在探讨云原生架构的核心理念、关键技术及其在实际项目中的应用实践,揭示其如何帮助企业实现更高效、更灵活、更可靠的IT系统构建与管理。通过深入剖析容器化、微服务、持续集成/持续部署(CI/CD)等核心技术,结合具体案例,本文将展现云原生架构如何赋能企业数字化转型,推动业务创新与发展。 ####
111 47
|
6天前
|
Kubernetes 负载均衡 Docker
构建高效后端服务:微服务架构的探索与实践
【10月更文挑战第20天】 在数字化时代,后端服务的构建对于任何在线业务的成功至关重要。本文将深入探讨微服务架构的概念、优势以及如何在实际项目中有效实施。我们将从微服务的基本理念出发,逐步解析其在提高系统可维护性、扩展性和敏捷性方面的作用。通过实际案例分析,揭示微服务架构在不同场景下的应用策略和最佳实践。无论你是后端开发新手还是经验丰富的工程师,本文都将为你提供宝贵的见解和实用的指导。
|
4天前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
29 10
|
5天前
|
监控 Cloud Native Java
云原生架构下微服务治理策略与实践####
【10月更文挑战第20天】 本文深入探讨了云原生环境下微服务架构的治理策略,通过分析当前技术趋势与挑战,提出了一系列高效、可扩展的微服务治理最佳实践方案。不同于传统摘要概述内容要点,本部分直接聚焦于治理核心——如何在动态多变的分布式系统中实现服务的自动发现、配置管理、流量控制及故障恢复,旨在为开发者提供一套系统性的方法论,助力企业在云端构建更加健壮、灵活的应用程序。 ####
46 10
|
3天前
|
缓存 运维 监控
后端开发中的微服务架构实践与挑战#### 一、
【10月更文挑战第22天】 本文探讨了微服务架构在后端开发中的应用实践,深入剖析了其核心优势、常见挑战及应对策略。传统后端架构难以满足快速迭代与高可用性需求,而微服务通过服务拆分与独立部署,显著提升了系统的灵活性和可维护性。文章指出,实施微服务需关注服务划分的合理性、通信机制的选择及数据一致性等问题。以电商系统为例,详细阐述了微服务改造过程,包括用户、订单、商品等服务的拆分与交互。最终强调,微服务虽优势明显,但落地需谨慎规划,持续优化。 #### 二、
|
5天前
|
运维 Cloud Native 持续交付
云原生架构下的微服务设计原则与实践####
【10月更文挑战第20天】 本文深入探讨了云原生环境中微服务设计的几大核心原则,包括服务的细粒度划分、无状态性、独立部署、自动化管理及容错机制。通过分析这些原则背后的技术逻辑与业务价值,结合具体案例,展示了如何在现代云平台上实现高效、灵活且可扩展的微服务架构,以应对快速变化的市场需求和技术挑战。 ####
25 7
|
6天前
|
消息中间件 Java API
微服务架构设计与实现:从理论到实践
微服务架构设计与实现:从理论到实践
29 7
|
3天前
|
监控 Cloud Native 测试技术
云原生架构下的性能优化与实践####
【10月更文挑战第21天】 本文深入探讨了在云原生环境下,如何通过一系列技术手段和最佳实践来提升应用性能。文章首先概述了云原生架构的基本原则与优势,随后详细分析了影响性能的关键因素,包括容器编排、微服务设计、持续集成/持续部署(CI/CD)流程以及监控与日志管理。针对这些因素,文中不仅介绍了具体的优化策略,如资源请求与限制的合理配置、服务间通信的高效实现、自动化测试与部署的优化,还结合案例分析,展示了如何在实际项目中有效实施这些策略以显著提升系统响应速度和处理能力。此外,文章还强调了性能测试的重要性,并提供了几种常用的性能测试工具和方法。最后,总结了云原生性能优化的未来趋势,为开发者和架构师
10 2
|
6天前
|
设计模式 API 持续交付
深入理解微服务架构:设计模式与实践
【10月更文挑战第19天】介绍了微服务架构的核心概念、设计模式及最佳实践。文章详细探讨了微服务的独立性、轻量级通信和业务能力,并介绍了聚合器、链式和发布/订阅等设计模式。同时,文章还分享了实施微服务的最佳实践,如定义清晰的服务边界、使用API网关和服务发现机制,以及面临的挑战和职业心得。
|
4天前
|
运维 Cloud Native API
云原生时代下的微服务架构实践
【10月更文挑战第22天】在数字化转型的浪潮中,云原生技术正以前所未有的速度重塑软件开发和运维的模式。微服务架构作为云原生的重要组成部分,其设计哲学、技术栈选择以及与传统单体应用的根本区别成为了现代软件工程讨论的焦点。本文将深入探讨微服务架构的核心概念,通过实际案例分析其在云平台下的应用,并分享在实施过程中的经验教训,旨在为读者提供一套清晰的微服务架构实践指南。