Kevinlma的博客

物来顺应,未来不迎,当时不杂,既过不恋

0%

wwdc optimizing_app_startup_time的记录

App build step

  1. 预处理,将宏替换,生成ASCIi中间文件
  2. 运行编译器,将中间文件生成汇编中间文件
  3. 运行汇编器,将汇编文件翻译生成目标文件 .o
  4. 运行链接器,将目标文件组合成一个可执行文件

Mach-O Terminology

File Types:

  • Executable – Main binary for application
  • Dylib – Dynamic library (.dylib, .dll, .so)
  • bundle – Dylib that cannot be linked, only dlopen(), e.g. plug-ins

Image – An executable, dylib or Bundle

Framework – Dylib with directories for resources and headers

Segments

File divided into segments:
__TEXT, __DATA, __LINEEDIT

All segments are multiples of page size. 16KB on arm64, 4KB else where.

Sections are a subrange of a segment

Common segments:

  • __TEXT has header, code, and read-only constants(c strings)
  • __DATA has all read-write content: globals, static variables, etc.
  • __LINKEDIT has meta data about how to load the program.

Mach-O Universal Files

Fat Header

  • One Page in size
  • Lists architectures and offsets

Tools and runtime support fat march-O files

Mach-O Image Loading

From exec() to main()

exec()

It’s a system call. Kernel maps maps your application into new address space.
Start of your app is random.

Low memory is marked inaccessible

  • 4KB+ for 32bit process
  • 4GB+ for 64-bit process
  • Catches NULL pointer usage
  • Catches pointer truncation errors.

What about Dylibs

Kernel loads helper program.

  • Dyld (dynamic loader)
  • Executions starts in dyld
    Dyld runs in-process
  • Loads dependent dylibs
  • Has same permissions as app

Dyld Steps

  • Map all dylibs, recurse
  • Rebase all Images
  • Bind all Images
  • ObjC prepare images
  • Run initializers

Loading Dylibs

  1. parse list of depentdent dylibs. (they are listed in the executable file header)
  2. find requested mach-o file
  3. open and read start of file
  4. validate mach-o
  5. register code signature (Code signatur means instructions can not be altered)
  6. call mmap() for each segment

Recursive Loading

  • All your indepedent dylibs are loaded.
  • Plus any dylib’s needed by those dylibs
  • Rinse and repeat

Apps typically load 100 to 400 dylibs!

  • Most are OS dylibs (they are always pre-cashed, and loaded very quickly)

Rebase and Bindings

Rebase : Ajusting pointers to within an image
binding: Setting pointers to outside image

1
2
//show rebase, bind information
xcrun dyldinfo -rebase -bind -lazy_bind myapp.app/myapp

大致理解是每个动态库中,指针地址的起始位置都是相对于内部基于0开始的,但是将库加载到App进程的内存空间后,整个库的指针起始位置是变了的。每个库都有自己的起始位置。所以有两个方面要调整:

  1. 动态库内部的地址引用需要调整, rebase
  2. 动态库之间的地址引用需要调整。binding

显然是与库中类的大小,个数都是相关的

Notify ObjC Runtime

  • Most ObjC set up done via rebasing and binding
  • All ObjC class definitions are registered
  • Non-fragile ivars offsets updated
  • Categories are inserted into method lists
  • Selectors are uniqued

Initializers

  • C++ generates initializers for statically allocated objects
  • ObjC +load Method
  • Run bottom up so each initializer can call dylibs below it
  • Lastly, Dyld calls main() in executable

Improve launch time

Goals

  • Duration varies on devices.
  • 400ms is a good target
  • don’t ever take longer than 20 seconds, or App will be killed by system

Test on the lowest supported device

Launch steps

parse images -> map images -> rebase images -> bind images -> run image initializers -> Call main() -> Call UIApplicationMain() -> Call applicationWillFinishLaunching

warm vs cold launch

warm launch

App and data are already in memory

cold launch

App is not in kernal buffer cache

Warm and cold launch times will be different.
Cold launch times are important.Measure cold launch time by rebooting.

measure pre-main

Measure before main() is difficult.
Dyld has built in mesurements

  • DYLD_PRINT_STATISTICS environment variable
  • Debugger pauses every dylib load. Dyld subtracts out debugger time

Dylib Loading

Embedded dylibs are expensive

Use fewer dylibs

  • Merge existing dylibs
  • Use static archives
  • dlopen() can cause issues, so it’s not recommended.

rebasing/binding

Reduce __DATA pointers

Reduce Objective C metadata

  • Classes, selectors, categories

Reduce C++ virtual

Use Swift structs

Examine machine generated codes

  • use offsets instead of pointers
  • Mark readonly

ObjC setup

  • Class registration
  • Non-fragile ivars offsets updated
  • Category registration
  • Selector uniquing

the same way as rebasing/binding.

Initializers -explicit

ObjC +load() method

  • Relplace with +initialize
    C/C++ attribute((constructor))

Replace with call site initializers

  • dispatch_once()
  • pthread_once()
  • std::once()

Initializers -implicit

C++ statics with non-trivial constructors

rewrite with Swift…

Do not cal dlopen() in initializers

Do not create thread in initializers