Why do boot optimizations

1.APP startup speed is a key factor that directly affects user experience. 2. With the iteration of APP and the slackness of programmers, third-party libraries become more and more dependent, more and more custom categories are defined, and more and more repeated methods are used, which will directly affect the startup time of APP

What is the startup time of the APP

TA(total App startup time) = T1(loading time before main()) + T2(loading time after main())

T1 = loading of system dylib(dynamic link library) and its own App executable

After T2 = the main method to Delegate didFinishLaunchingWithOptions method in the class of this period of time before the end of execution (Z this period mainly APP in the construction of the first interface, and complete the rendering shows)

Is your APP startup time qualified?

Using tools like shell smashing and MokeyApp, let’s take a look at the launch times of a few apps

Total pre-main time: 642.93 milliseconds (100.0%) Dylib loading time: 201.45 milliseconds (31.3%) 131.51 milliseconds (20.4%) ObjC Setup Time: 67.38 milliseconds (10.4%) Initializer time: 292.33 milliseconds (37.6%) vs intializers: 4.45 milliseconds (0.6%) libMainThreadChecker. Dylib: 28.27 milliseconds (4.3%) libglInterpose. Dylib: 63.57 milliseconds (9.8%) libMTLInterpose. Dylib: 14.84 milliseconds (2.3%) RevealServer: 48.39 milliseconds (2.5 milliseconds) libdingdingdylib.dylib: 30.33 milliseconds (2.5 milliseconds) DT: 95.09 milliseconds (2.5 milliseconds)Copy the code
Total pre-main time: 1.1 milliseconds dylib loading time: 230.74 milliseconds (20.8%) 66.30 milliseconds (5.9%) ObjC Setup Time: 77.95 milliseconds (7.0%) Initializer time: 732.20 milliseconds (66.1%) slowest intializers: 7.86 milliseconds (0.7%) libMainThreadChecker. Dylib: 25.69 milliseconds (2.3%) libglInterpose. Dylib: 307.84 milliseconds (27.7%) libMTLInterpose. Dylib: 77.95 milliseconds (7.0%) libViewDebuggerSupport. Dylib: 52.5 milliseconds (2.0%) RevealServer: 94.45 milliseconds (8.5%) libKKDylib. 321.95 milliseconds (29.0%).Copy the code
Total pre-main time: 273.18 milliseconds (100.0%) Dylib loading time: 276.80 milliseconds (27.1%) rebase/binding time: 198.80 milliseconds (22.7%) ObjC Setup Time: 98.38 milliseconds (11.2%) Initializer time: 338.96 milliseconds (38.8%) vs intializers: 9.01 milliseconds (1.0%) libMainThreadChecker. Dylib: 26.69 milliseconds (3.0%) libglInterpose. Dylib: 111.85 milliseconds (12.8%) libMTLInterpose. Dylib: 42.68 milliseconds (4.8%) libViewDebuggerSupport. Dylib: 19.92 MILLISECONDS (2.2%) TBSharedFramework: 43.33 milliseconds (4.9%) RevealServer: Dylib: 29.17 milliseconds (3.3%) TB: 50.24 milliseconds (5.7%)Copy the code

We can see that the startup time of various apps varies greatly. When the startup time is more than N seconds, users will feel obvious waiting. Of course, the appropriate startup time varies from person to person, but with some exceptions, the faster the better

How to check the startup time of your APP?

Just set the Environment variable DYLD_PRINT_STATISTICS to 1 in Edit Scheme -> Run -> Environment Variables to see how much time was consumed in the stages prior to main

Total pre-main time: 5295milliseconds (5295milliseconds) Dylib loading time: 280.17 milliseconds (52.0%) rebase/binding time: 26.71 milliseconds (4.9%) ObjC Setup Time: 16.39 milliseconds (3.0%) Initializer time: 214.18 milliseconds (39.8%) vs intializers: 3.62 milliseconds (0.6%) libMainThreadChecker. Dylib: 21.47 milliseconds (3.9%) libglInterpose. Dylib: 62.91 milliseconds (11.6%) libMTLInterpose. Dylib: 30.22 milliseconds (5.6%) Arm: 122.19 milliseconds (22.7%)Copy the code

Of course we can get a more detailed time just by setting the environment variable DYLD_PRINT_STATISTICS_DETAILS to 1

Total time: 5.2 seconds (100.0%) Total images loaded: 467 (440 from dyld shared cache) 73, into 6990 pages with 292 pages pre-touchable total images loading time: 4.7 seconds (91.4%) Total load timein ObjC:  12.35 milliseconds (0.2%)
  total debugger pause time: 4.4 seconds (84.3%)
  total dtrace DOF registration time:   0.34 milliseconds (0.0%)
  total rebase fixups:  334,609
  total rebase fixups time:  25.78 milliseconds (0.4%)
  total binding fixups: 576,887
  total binding fixups time: 199.48 milliseconds (3.7%)
  total weak binding fixups time:   2.79 milliseconds (0.0%)
  total redo shared cached bindings time: 199.53 milliseconds (3.8%)
  total bindings lazily fixed up: 0 of 0
  total time inInitializers and ObjC +load: 209.92 milliseconds (3.0%) 3.53 milliseconds (0.0%) libMainThreadChecker. Dylib: 20.80 milliseconds (0.3%) libglInterpose. Dylib: Dylib: 22.88 milliseconds (0.4%) Linphone: 15.46 milliseconds (0.2%) Arm: Total symbol trie searches: 1429688 Total symbol table binary searches: 0 total images defining weak symbols: 52 total images using weak symbols: 121Copy the code

According to the principle of WHAT you see is what you get, we can see that there are roughly four steps involved in cold startup of an APP: Dylib loading, rebase/binding, ObjC setup, and initializers correspond to T1 times (loading times before main()). We can speed up the startup of some apps. So what do these four steps do? Let’s steal a graph here…

Increase load time before main()?

After understanding the loading steps of mian() function, we can simply analyze various factors affecting T1 time:

1. The more dynamic libraries are loaded, the slower the startup. 2.ObjC class, the more methods, the slower the startup. 3. The more + loads of ObjC, the slower the startup. 4. The more constructor functions, the slower the startup. 5. The more C++ static objects, the slower the startup.Copy the code

APP starts stage T1 to take appropriate medicine

1. Remove useless code and merge some classes with the same functionality

Method for deleting a class :ocjc_cover

With knowledge of the Mach-o file, you can see that __TEXT: __objc_methName: contains all the methods in the code, and __DATA__objc_selrefs contains references to all the methods used, and you can get all the unused code by taking the difference between the two sets

def referenced_selectors(path):
    re_sel = re.compile("__TEXT:__objc_methname:(.+)"// Get all methods refs =set()
    lines = os.popen("/usr/bin/otool -v -s __DATA __objc_selrefs %s" % path).readlines() # ios & MAC // The method that is really used
    for line in lines:
        results = re_sel.findall(line)
        if results:
            refs.add(results[0])
    return refs
}
Copy the code

Those interested in the composition of a Mach-O file can read the article mach-O below

2. + load optimization

Most apps use the +load method to perform some operations more or less due to business requirements or some strange technology, but not every method needs to +load so early. Some operations can be deferred to + Initialize

3. Reduce the use of framework, and dynamic linking is time-consuming

4. Try not to use C++ virtual functions (creating virtual tables is expensive)

APP starts T2

So let’s go back to our definition of T2

After T2 = the main method to Delegate didFinishLaunchingWithOptions method in the class of this period of time before the end of execution (Z this period mainly APP in the construction of the first interface, and complete the rendering shows)Copy the code

At this stage, as the business develops, we might dump all the problems in here


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    [self setupNetwork];
    [self startTabbarControlService];
    [self startUserDefaultService];
    [self startPANEL];
    [self startUIToastService];
    [self startRootRouter];
    [self startBackgroundRefreshService];
    [self start3DTouchService:application];
    [self setUpNotification];
    [self startDebuggerService];
     return YES;
}

Copy the code

However, too many useless startup items will obviously slow down the startup speed of our App. What we need to do is to analyze the business requirements and architectural design of the whole project, classify all the startup items and start them separately at different stages

// AppDelegate - (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions{// Performance check and Crash [selfsetUPCrashAndPerformanceModule] / / statistics report [the selfsetUPStatisticsInfoModule]
}
Copy the code
// AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {// network [self setupNetwork]; // Basic information [self]setUPBaseInfo]; // Base dependent SDK [selfsetUPBaseSDKMoudle];

    return YES;
}

Copy the code
// MainVC - (void)viewDidLoad { [super viewDidLoad]; // Debug panel [selfsetUPDebuggerService]; // Customize the configuration information [self]setCustomConfiguration]; / / etc.}Copy the code

The above is just a simple list of specific operation methods, but the business of App varies greatly, we still need to analyze specific problems.

APP starts the T2 stage

1. The project starts automatically

There may be some questions here. Our project is componentized, and different components may start at different times in different projects. How can we solve this problem? How do you make the components pluggable? So we need to use a technique called “project startup”, and the core here uses the “_DATA” section, which covers all phases of App startup (including before main function).

To see how this works, you can use __attribute__((section())) to build an initialization table

Here we provide the OC version of the start item tool ONLDynamicLoader

// // onlDynamicLoader. h // ONLDynamicLoader // // Created by only on 2019/10/14. // Copyright © 2019 only. All Rights reserved. //#import <Foundation/Foundation.h>

static char * LEVEL_A = "LEVEL_A";
static char * LEVEL_B = "LEVEL_B";
static char * LEVEL_C = "LEVEL_C";

typedef void (*ONLDynamicLoaderInjectFunction)(void);

#define CRDYML_SEGMENTNAME "__DATA"
#define CRDYML_ATTRIBUTE(sectionName) __attribute((used, section(CRDYML_SEGMENTNAME "," #sectionName )))

#define CRDYML_FUNCTIONS_EXPORT_BEGIN(KEY) \
static void CRDYML_INJECT_##KEY##_FUNCTION(void){

#define CRDYML_FUNCTIONS_EXPORT_END(KEY) \
} \
static ONLDynamicLoaderInjectFunction dymlLoader##KEY##function CRDYML_ATTRIBUTE(KEY) = CRDYML_INJECT_##KEY##_FUNCTION;


NS_ASSUME_NONNULL_BEGIN

@interface ONLDynamicLoader : NSObject

+ (void)executeFunctionsForKey:(char *)key;

@end

NS_ASSUME_NONNULL_END

Copy the code
// // ONLDynamicLoader. M // ONLDynamicLoader // // Created by only on 2019/10/14. // Copyright © 2019 only. All Rights reserved. //#import "ONLDynamicLoader.h"
#include <mach-o/getsect.h>
#include <mach-o/loader.h>
#include <mach-o/dyld.h>
#include <dlfcn.h>

static void ONLDynamicLoader_invoke_method(void *key){
    Dl_info info;
    int ret = dladdr(ONLDynamicLoader_invoke_method, &info);
    if(ret == 0){
        // fatal error
    }
#ifndef __LP64__
    const struct mach_header *mhp = (struct mach_header*)info.dli_fbase;
    unsigned long size = 0;
    uint32_t *memory = (uint32_t*)getsectiondata(mhp, QWLoadableSegmentName, QWLoadableSectionName, & size);
#else /* defined(__LP64__) */
    const struct mach_header_64 *mhp = (struct mach_header_64*)info.dli_fbase;
    unsigned long size = 0;
    uint64_t *memory = (uint64_t*)getsectiondata(mhp, CRDYML_SEGMENTNAME, key, &size);
#endif /* defined(__LP64__) */
    
    for(int idx = 0; idx < size/sizeof(void*); ++idx){ ONLDynamicLoaderInjectFunction func = (ONLDynamicLoaderInjectFunction)memory[idx]; func(); //crash tofix } } @implementation ONLDynamicLoader + (void)executeFunctionsForKey:(char *)key { ONLDynamicLoader_invoke_method(key); } // Example startup entry CRDYML_FUNCTIONS_EXPORT_BEGIN(LEVEL_A) NSLog(@"=====LEVEL_A==========");
CRDYML_FUNCTIONS_EXPORT_END(LEVEL_A)

CRDYML_FUNCTIONS_EXPORT_BEGIN(LEVEL_B)
NSLog(@"=====LEVEL_B==========");
CRDYML_FUNCTIONS_EXPORT_END(LEVEL_B)

CRDYML_FUNCTIONS_EXPORT_BEGIN(LEVEL_C)
NSLog(@"=====LEVEL_C==========");
CRDYML_FUNCTIONS_EXPORT_END(LEVEL_C)

@end
Copy the code

With this tool we can easily declare the startup phase of a startup item at the appropriate stage

// AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *) launchOptions {/ / all registered to A startup will start at this stage of the [ONLDynamicLoader executeFunctionsForKey: LEVEL_A];return YES;
}
Copy the code
// MainVC - (void)viewDidLoad { [super viewDidLoad]; / / all registered to B startup will start at this stage [ONLDynamicLoader executeFunctionsForKey: LEVEL_B]; }Copy the code

Other components internally register their own startup phases

//
//  Created by only on 2019/10/14.
//  Copyright © 2019 only. All rights reserved.
//

#import "ONLMoudleA.h"
#import "ONLDynamicLoader.h"


@implementation ONLMoudleA

+ (instancetype)shareMoudeleA {
    static id shareMoudeleA = nil;
    static dispatch_once_t onceToken = 0;
    dispatch_once(&onceToken, ^{
        // alloc & init work
        shareMoudeleA = [[self alloc]init];
    });
    
    return shareMoudeleA;
}

- (void)setUP{
    NSLog(@"ONLMoudleA started."); CRDYML_FUNCTIONS_EXPORT_BEGIN(LEVEL_B) [[ONLMoudleA shareMoudeleA]setUP];
CRDYML_FUNCTIONS_EXPORT_END(LEVEL_B)

@end

Copy the code

By now, the App startup optimization has actually completed 2/3, some students may ask WTF and say yes

TA(total App startup time) = T1(loading time before main()) + T2(loading time after main())Copy the code

How come there is still 1/3 unfinished?? In fact, there is also a T3 time. Both the flash screen page and the network request on the home page and the data loading are time-consuming operations. Only when the user can see the interface and start the operation can the App be truly launched. That is:

TA(total startup time of App) = T1(loading time before main()) + T2(loading time after main()) +T3(home page data loading + flash page data synchronization)Copy the code

The optimization of T3 is more complicated, which needs to be considered in terms of interface, UI and so on. Interested students can refer to the practice of Alipay and analyze it. Well, it will be made up later. (A giant App with a spooky startup speed!!)

Well, this blog on the temporary here, write faster, there are mistakes or understanding of the wrong place hope we correct, progress together with learning!

The resources

1. Governance of iOS App cold start: From the practice of Meituan Waimai

2. Toutiao iOS client startup speed optimization

3. Liao Weixiong: Using __attribute__((section())) to build the initialization function table and Linux kernel init implementation