preface

There are three types of startup: Cold startup, Warm startup, and Resume

For each of the three boot-ups, the corresponding timing and the state of the resource in memory are shown below

Start the process

What does the App lifeCyle look like from the moment the user clicks on the icon to the first frame? The diagram below.

PS: If you look at the whole startup life cycle with the Instuments App Launch tool, there’s a difference. There was another stage before the System Interface, which actually only consumed 5ms. The other hundreds of milliseconds were all extra time generated by the App Launch tool itself to track data, which can be ignored.

The process can be divided into two parts: system initialization and App initialization. It’s called pre-main and after main

Pre-main

There are two stages

  • System Interface
  • Runtime Init
    System Interface
    • Dyld initialization
    • Dyld loads the dynamic library
    • Redirection of the rebase/binding symbol is bound to the symbol
    Runtime Init
    • Initialization of Runtime (catoray load)
    • The load method of all classes executes

After-main

There are three stages

  • UIKit Init
  • Application Init
  • Inital Frame Render
    UIKit Init
    • Instantiate the Application
    • System event handling, including Runloop
    Application Init
    • Initialization of the Application
    • Call application: willFinishLaunchingWithOptions: application: didFinishLaunchingWithOptions:
    Inital Frame Render
    • Layout calculation
    • Frame 1 render

That’s what the whole startup process does, and there’s really not much we can control at these stages.

In the pre-main phase

The only things we can affect are the number of dynamic and static libraries and the execution time of the load method.

Dynamic and static library

The difference between

Product level
The type of library format The size of the Framework format
Static library .a .framework Large (multiple Mach-Os) Headers +.a + signature + resource file
The dynamic library .framework Small (only one Mach-O) Headers +.dylib + signature + resource file
Compile into the host App level
The type of library Compiles into App and compares the impact on App size Link the timing
Static library 2. The compilation stage has been linked into the App, so there will be less data needed in the linking process Compilation phase
The dynamic library big Operation phase

How does the object file determine the address of the calling method

Normal methods that call from one file to another, such as the run() function in dog.c from main.c, will generate an undefined symbol called run(undefine symbol) in the main.o file, whose address will be determined when linking

OC is a dynamic language, for example, when main.m calls a method in dog. m, the [[Dog alloc]init], main.o file will only generate Dog (undefine symbol), but not the undefined symbol table of alloc and init. The reason is that the OC method is determined at run time when it is finally implemented.

The above knowledge of dynamic and static libraries, in fact, has explained why the number of dynamic and static libraries affects the startup time

  1. Firstly, dyLD takes a long time to load the dynamic library. If there are too many dynamic libraries, the loading time will obviously be long

  2. Also, if there are too many dynamic libraries, the rebinding time will increase

  3. For static libraries, the corresponding rebase time increases.

    • Due to the presence of ALSR, the internal symbolic address needs to be fixed
    • The more static library files you have, the more symbols you need to fix internally
  4. In summary, rebase and Rebinding are a pair of time-exclusive operations

  5. However, Dyld load + rebinding takes more time than Rebase, so reducing dynamic libraries is effective for startup time optimization

This is the phase after Main

Try to put time-consuming operations into child threads and minimize Page faults.

Binary rearrangement

How does a computer run a program?

The first computers used physical memory directly and loaded the program’s data into memory at once.

In the days of low memory capacity, doing so would have resulted in destroying the original application in order to open a new one. The memory usage is low.

As a result, the application was later paginated. The data is loaded into memory each time it is run to the required program data. This solves the problem of low memory utilization.

As time goes on, another problem arises, which is to use physical memory directly and easily change the memory address of other programs.

Thus ushered in the era of virtual memory, the system has opened up the same size of virtual memory for each program, virtual memory address and physical memory address directly corresponding, determined by the system mapping relationship. This eliminates the problem of direct access.

However, there is another problem. The virtual memory address assigned to each application is the same every time. As a result, each should be easily hooked off. The result is a random offset address, which is added to the virtual memory address at random each time the program starts, so that the memory address is random each time.

This is ASLR, which is why you need rebase every time you start.

Page Fault

In order to improve memory utilization, the data of the program is pagination. Every time the binary data is retrieved from the hard disk and loaded into memory, an exception occurs, which is called a Page Fault. Of course, a Page Fault can block for only a few milliseconds, and normally the block is invisible. However, if there are hundreds or thousands of Page faults at startup, this time will be sensed. This is why solving the Page Fault can improve startup speed.

How do I reduce Page Faults during startup?

A single or small number of Page faults are almost imperceptive at the level of user perception. A large number of Page faults occur only when the program initializes a lot of data at startup, so you can reduce lookup and load time by simply putting together the binary data called to the method at startup.

Before the iPhone 6s, one page was 4KB, and after the iPhone 6s, one page was 16KB

Clane static piling

Now that you know how to reduce Page faults, how do you know which methods are called during startup?

It just so happens that Clane and Swiftc, the two front-end compilers, already support fetching any function we call.

If you want to take a closer look at how compilation supports fetching calls to arbitrary functions, you can trace them through assembly. The compiler inserts these two methods into our function (this code can be found on Clane’s website).

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
                                        uint32_t *stop)
void __sanitizer_cov_trace_pc_guard(uint32_t *guard)
Copy the code

Now that you want to get what methods are called, you need to have a cutoff save time. The adjusted according to the different business, after applicationDidFinishLaunching cut-off, of course can also be shown at the first frame.

Finally, you get a string list file of the methods that were finally called, and then you configure the compiler to reorder the binaries based on that file

The optimization results

The dynamic library

To change the static library

To optimize the results of the data

  1. Most dynamic libraries do see a 200-300 ms improvement in startup time after switching to static libraries.
  2. Binary rearrangement doesn’t work as well as the web says, and I can’t even verify that it’s improved.

PS: The above steps of verification and optimization are all tests conducted without any process after the phone is restarted. After all, as Apple said at WWDC, 🍎 equals 🍎

The reason why the effect of binary rearrangement is not obvious is analyzed

  1. Maybe the compiler has been optimized by now
  2. The project itself, between main and didFinishLaunching, doesn’t initialize much data. After all, everything that can be postponed has been postponed.

The binary rearrangement process is not cumbersome. As for changing a dynamic library to a static library, there are still some manipulations. I’ll talk about that separately.