常用开发工具🛠app优化

iOS 二进制重排

2021-05-10  本文已影响0人  just东东

iOS 应用启动优化

1.启动

1.1 冷启动

1.2 热启动

1.3 查看启动时间

Scheme -> Edit Scheme -> run -> Arguments

在Environment Variables 添加名称为 DYLD_PRINT_STATISTICS 的一项。

将自己的程序运行在真机设备上,查看启动时间(Main函数前)

Total pre-main time: 1.0 seconds (100.0%)
         dylib loading time: 252.32 milliseconds (24.8%)
        rebase/binding time:  13.32 milliseconds (1.3%)
            ObjC setup time: 356.37 milliseconds (35.1%)
           initializer time: 392.66 milliseconds (38.6%)
           slowest intializers :
             libSystem.B.dylib :   8.38 milliseconds (0.8%)
    libMainThreadChecker.dylib : 174.17 milliseconds (17.1%)
                    SchemeName : 323.75 milliseconds (31.9%)

slowest intializers: 比较耗时的操作是:libSystem.B.dylib 系统库,libMainThreadChecker.dylib 调试库。加载的时候不需要我们考虑。剩下的比较耗时的就是我们的主程序的加载了。

2.main之后

2.1 打点计时框架

BLStopwatch 里面有中文注释。

2.2 main之后的优化建议

2.3 pre-main的优化(main函数前)

iOS 二进制重排

注:本文只介绍实现步骤。
详细的原理与背景详见 字节跳动技术团队分享的文章:

抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15%

1. 获取Linkmap

Target -> BuildSettings -> Linking -> Write Link Map File
修改为YES 即会在上面的路径写入Linkmap文件

image
几种查看符号的命令,还是以Link Map的顺序为主
cd ../DemoName.app
nm DemoName 查看此Demo的符号
nu -Up DemoName  查看此Demo的符号(自定义)
nu -up DemoName  查看此Demo的符号(系统)

2.核心原理

2.1 ld

Xcode 的连接器是ld, ld有一个不常用的参数-order_file,通过man ld可以看到详细文档:

Alters the order in which functions and data are laid out. For each section in the output file, 
any symbol in that section that are specified in the order file file is moved to the start of its 
section and laid out in the same order as in the order file file.

可以看到,order_file中的符号会按照顺序排列在对应section的开始,完美的满足了我们的需求。

配置符号在order_file 文件里,ld 会根据配置的顺序进行连接,最后达到符号加载到内存的顺序改变,满足优先调用的方法整体排在前面,减少Page Fault,从而达到启动优化。如果order_file中的符号实际不存在, ld会忽略这些符号,如果提供了link选项-order_file_statistics,会以warning的形式把这些没找到的符号打印在日志里。

2.2 Xcode 配置order file:

image

3. 符号获取

原理很简单,但是如何获得符号的顺序才是最难的。

3.1 通过hook方式

我们可以hook所有方法的调用,因为OC 方法的调用本质是发送 Objc_msgSend() 函数,所以我们可以 hook Objc_msgSend() 函数,但是这个函数是可变参数的,要想获取内部参数,需要通过汇编代码,从寄存器获得参数,这就加大了难度。另外还有一些C++函数,Swift的获取也不能通过Objc_msgSend() 函数。另外block作为一个特殊的单元,也不走Objc_msgSend() 函数,所以需要单独 hook。总之有很多瓶颈,详细的hook和使用可参考字节跳动技术团队分享的那篇文章

3.2 编译器插桩(Clang插桩)

Clang 文档

3.2.1 Xcode 配置

-fsanitize-coverage=trace-pc-guard
-fsanitize-coverage=func,trace-pc-guard
-sanitize-coverage=func
-sanitize=undefined

3.2.2 主要hook函数

来自Clang 文档的示例代码

// trace-pc-guard-cb.cc
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>

// This callback is inserted by the compiler as a module constructor
// into every DSO. 'start' and 'stop' correspond to the
// beginning and end of the section with the guards for the entire
// binary (executable or DSO). The callback will be called at least
// once per DSO and may be called multiple times with the same parameters.
extern "C" 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 = ++N;  // Guards should start from 1.
}

// This callback is inserted by the compiler on every edge in the
// control flow (some optimizations apply).
// Typically, the compiler will emit the code like this:
//    if(*guard)
//      __sanitizer_cov_trace_pc_guard(guard);
// But for large functions it will emit a simple call:
//    __sanitizer_cov_trace_pc_guard(guard);
extern "C" void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;  // Duplicate the guard check.
  // If you set *guard to 0 this code will not be called again for this edge.
  // Now you can get the PC and do whatever you want:
  //   store it somewhere or symbolize it and print right away.
  // The values of `*guard` are as you set them in
  // __sanitizer_cov_trace_pc_guard_init and so you can make them consecutive
  // and use them to dereference an array or a bit vector.
  void *PC = __builtin_return_address(0);
  char PcDescr[1024];
  // This function is a part of the sanitizer run-time.
  // To use it, link with AddressSanitizer or other sanitizer.
  __sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

3.2.3. 自动生成order文件

NSMutableArray <NSString *> * symbolNames = [NSMutableArray array];
    
    while (YES) {
        SYNode * node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
        if (node == NULL) {
            break;
        }
        Dl_info info;
        dladdr(node->pc, &info);
        NSString * name = @(info.dli_sname);
        BOOL  isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
        NSString * symbolName = isObjc ? name: [@"_" stringByAppendingString:name];
        [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];
        }
    }
    //干掉自己!
    [funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
    //将数组变成字符串
    NSString * funcStr = [funcs  componentsJoinedByString:@"\n"];
    
    NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"hank.order"];
    NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
    NSLog(@"%@",funcStr);
特别提醒!

获取order文件后可以删除上述配置和文件

上一篇 下一篇

猜你喜欢

热点阅读