前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >iOS启动优化~~~二进制重排

iOS启动优化~~~二进制重排

作者头像
大话swift
发布2021-01-18 09:53:00
6030
发布2021-01-18 09:53:00
举报
文章被收录于专栏:大话swift大话swift

项目赶了大半年基本进入的稳定期,剩下的就是按需的小步快跑的迭代周期,基本上半个月一个小版本,可以抽时间做一些优化型操作。正好看到抖音的启动优化文案----二进制重排,顺便就做个记录。

1 项目配置

1.1 开启 Write Liink Map File

默认情况下文件会被写在编译项目下按照对的指令集写入文件

2 文件查看

打开对应的文件 我们搜索"Symbols"

这些便是写进入的对应的文件符,这些文件如何得来的呢?和Name顺序如何来的呢?

我们大胆猜测是由文件的编译顺序导致,我们先添加一个NSObject的category--NSObject+LaunchOrder.h

代码语言:javascript
复制
@interface NSObject (LaunchOrder)
- (void) method1;
- (void) method2;
@end

调整Build Phases--Compile Souurces的文件顺序--将NSObject+LaunchOrder置顶

运行后我们重新打开launch.order文件

有此可见order中Symbols与我们的文件编译顺序相关联。那么我们怎么处理来达到项目启动优化呢?

2 理论原理

2.1 缺页中断

App启动时会将对应的符号加载如内存,iOS中默认一次按照4k空间加载符号文件,假如一次无法加载完成启动需要的资源符号,那么就会出现缺失,需要加载更多page,这个重新寻址加载是相对耗时的。假如将分散的符号按照App的启动顺序需要按需排列,那么就可减少启动耗时。

未分配前

分配后

上图展示的例子未优化前App启动共发生4次缺页中断,分配后只需1次就好,而现实中我们的项目往往更加复杂需要加载的就更多,累计起来的时间也会相对可观

3 文件符号收集

3.1 clang插桩获取符号

1 添加编译 设置

代码语言:javascript
复制
#import <stdint.h>
#import <stdio.h>
#import <sanitizer/coverage_interface.h>
#import <libkern/OSAtomic.h>
#import <dlfcn.h>

void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
  static uint64_t N;  // Counter for the guards.
  if (start == stop || *start) return;  // Initialize only once.
//  printf("INIT: %p %p\n", start, stop);
  for (uint32_t *x = start; x < stop; x++)
    *x = (uint32_t)++N;  // Guards should start from 1.
}

void printInfo(void *PC) {
    Dl_info info;
    dladdr(PC, &info);
    printf("fnam:%s \n fbase:%p \n sname:%s \n saddr:%p \n",
           info.dli_fname,
           info.dli_fbase,
           info.dli_sname,
           info.dli_saddr);
}

static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;

typedef struct {
    void *pc;
    void *next;
} SymbolNode;

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
    if (!*guard) return;  // Duplicate the guard check.
    
    void *PC = __builtin_return_address(0);
    
    SymbolNode * node = malloc(sizeof(SymbolNode));
    *node = (SymbolNode){PC, NULL};
    
    OSAtomicEnqueue(&symbolList, node, offsetof(SymbolNode, next));
//    printInfo(PC);
}

上述只是获取到对应的符号,我们需要将他们翻译出来

代码语言:javascript
复制

+ (BOOL)exportSymbolsWithFilePath:(nonnull NSString *)filePath
{
    NSMutableArray <NSString *>* symbolNames = [NSMutableArray array];
    while (YES) {
        SymbolNode *node = OSAtomicDequeue(&symbolList, offsetof(SymbolNode, next));
        if (node == NULL) {
            break;
        }
        Dl_info info;
        dladdr(node->pc, &info);
        
        NSString * name = @(info.dli_sname);
        BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["]; // Objective-C method do nothing
        NSString * symbolName = isObjc? name : [@"_" stringByAppendingString:name]; // c function with "_"
        [symbolNames addObject:symbolName];
    }
    
    NSEnumerator * emt = [symbolNames reverseObjectEnumerator];
    NSMutableArray<NSString*>* funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
    NSString * name;
    while (name = [emt nextObject]) {
        if (![funcs containsObject:name]) {
            [funcs addObject:name];
        }
    }
    // remove current method symbol (not necessary when launch)
    [funcs removeObject:[NSString stringWithFormat:@"%s", __FUNCTION__]];
    
    NSString *funcStr = [funcs componentsJoinedByString:@"\n"];
    NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
        [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
    }
    return [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
}

将上述函数写到

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 函数末尾即可获取到App启动到首屏显示所需要的符号

代码语言:javascript
复制
_main
-[AppDelegate application:didFinishLaunchingWithOptions:]
+[SMLagMonitor shareInstance]
___29+[SMLagMonitor shareInstance]_block_invoke
-[SMLagMonitor beginMonitor]
-[SMLagMonitor setIsMonitoring:]
-[SMLagMonitor setCpuMonitorTimer:]
___copy_helper_block_e8_32s
___28-[SMLagMonitor beginMonitor]_block_invoke
-[AppDelegate setWindow:]
-[SMRootViewController init]
-[AppDelegate styleNavigationControllerWithRootController:]
+[SMStyle colorPaperBlack]
+[UIColor(UIColor_Expanded) colorWithHexString:]
+[UIColor(UIColor_Expanded) colorWithRGBHex:]
+[SMStyle colorPaperDark]
_CGRectMake
-[AppDelegate window]
-[UIViewController(clsCall) clsCallHookViewWillAppear:]
-[UIViewController(clsCall) clsCallInsertToViewWillAppear]
+[SMCallTrace startWithMaxDepth:]
_smCallConfigMaxDepth
+[SMCallTrace start]
_smCallTraceStart

最后将文件粘贴入launch.order中即可实现加载的重排(下图是objc文件参考)

收集重排符号

项目编译后的符号文件

对比我们的收集到的首屏启动完成后的符号与编译项目符号一致,从而通过减少缺页中断达到优化启动的目的

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大话swift 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档