二进制重排

原理

App启动和内存加载

Linux 系统下,进程申请内存并不是直接物理内存给我们运行,而是只标记当前进程拥有该段内存,当真正使用这段段内存时才会分配,此时的内存是虚拟内存。

在虚拟内存出现前,程序指令必须都在物理内存内,使得物理内存能存放的进程十分有限,并且由于是相邻存储,容易发生越界访问等情况。

虚拟内存是作为 内存的管理和保护工具 诞生的,为每个进程提供了一片连续完整的虚拟内存空间,使用时先通过界限寄存器判断访问是否越界,再通过基址寄存器转换为实际内存地址。降低了内存管理的复杂度,保护每个进程的内存地址空间不会被其它进程破坏,并且实现了 共享缓存功能,访问时先判断是否已缓存到主存中才通过 CPU 寻址(虚拟地址)访问主存或硬盘。

当我们需要访问一个内存地址时,如果虚拟内存地址对应的物理内存还未分配,CPU 会执行 page fault,将指令从磁盘加载到物理内存中并进行验签操作(App Store 发布情况下)。

二进制重排,主要是优化我们启动时需要的函数非常分散在各个页,启动时就会多次Page Fault造成时间的损耗

Page Fault

进程如果能直接访问物理内存无疑是很不安全的,所以操作系统在物理内存的上又建立了一层虚拟内存。为了提高效率和方便管理,又对虚拟内存和物理内存又进行分页(Page)。当进程访问一个虚拟内存 Page 而对应的物理内存却不存在时,会触发一次缺页中断(Page Fault),分配物理内存,有需要的话会从磁盘 mmap 读人数据。

通过 App Store 渠道分发的 App,Page Fault 还会进行签名验证,所以一次 Page Fault 的耗时比想象的要多:

二进制重排原理

在虚拟内存部分,我们知道,当进程访问一个虚拟内存page,而对应的物理内存不存在时,会触发缺页中断(Page Fault),因此阻塞进程。此时就需要先加载数据到物理内存,然后再继续访问。这个对性能是有一定影响的。

在App 启动过程中,会调用各种函数,由于这些函数分布在各个 TEXT 段中且不连续,此时需要执行多次 page fault 创建分页,将代码读取到物理内存中,并且这些分页中的部分代码不会在启动阶段被调用。如下图所示,假设我们在启动阶段需要调用 Func A、B、C,则需执行3次 page default(包括首次读取),并使用3个分页。

重排

编译器在生成二进制代码的时候,默认按照链接的 Object File(.o)顺序写文件,按照 Object File 内部的函数顺序写函数。

静态库文件.a 就是一组.o 文件的 ar 包,可以用ar -t查看.a 包含的所有.o。

img

默认布局

简化问题:假设我们只有两个 page:page1/page2,其中绿色的 method1 和 method3 启动时候需要调用,为了执行对应的代码,系统必须进行两个 Page Fault。

但如果我们把 method1 和 method3 排布到一起,那么只需要一个 Page Fault 即可,这就是二进制文件重排的核心原理。

img

Reference

1. 深入探索 iOS 启动速度优化(二进制重排)

results matching ""

    No results matching ""