iOS-性能优化

二十六、 启动优化,二进制重排

2020-11-23  本文已影响0人  Mjs

冷启动&热启动

启动优化

测试APP启动一般通过main函数分割成两个阶段。main函数之前通过系统反馈给我们,main函数之后我们可以自己统计。

main函数之前

打印启动时间

main函数之后
  1. 减少初始化流程,能懒加载就懒加载
  2. 减少遗留不用的代码
  3. 启动时候的加载放在多线程去做
  4. 启动的时候的页面最好用纯代码去做

iOS内存分为物理内存和虚拟内存
虚拟内存 : 解决安全问题、解决内存使用率问题

image image

内存分页技术
分配4G大小假地址
MacOS 、linux (4K为一页)
iOS(16K为一页)

生成LinkMap文件

打开LinkMap文件

image

file文件顺序就是在Compile Sources决定的

image image image

程序启动调用的放法分别分布在不同的分页中,这就导致启动的时候有大量的缺页中断(PageFault)。

优化思路
将所有启动时刻需要调用的方法排列在一块。这就是二进制重排

查看PageFault(缺页中断)次数

1、command+I

image

2、选择System Trace

image

3、点击一下,第一个页面出现后,再点击一下

image image

4、搜索Main Thread

image

5、选择Main Thread、选择Virtual Memory。File Backed Page in 就是PageFault

image

二进制重排优化是在链接阶段对即将生成的可执行文件进行重新排列

order_file

1、打开objc4-750源码

image

libobjc.order存放的是方法的调用顺序

libobjc.order.png

苹果公司对源码也进行了二进制重排,从这里就可以看到方法的调用流程。
现在我们对自己的程序进行重排。
我们通过终端进入当前项目目录下

touch hank.order

新建个hank.order文件,输入

_main
-[ViewController viewDidLoad]
-[hank hello]
_test1

Build Settings中搜索order file,将我们的文件添加进去。

orderFile.png

编译一下查看linkMap

重排后的linkMap.png
我们可以看到,现在的顺序是我们在order文件里添加的顺序,没有的方法会被忽略掉。

clang插庄

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);
[图片上传中...(Snip20200420_13.png-54b663-1587378588681-0)]
  for (uint32_t *x = start; x < stop; x++)
    *x = ++N;  // Guards should start from 1.
}

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);
}

image 读取头尾.png

读头 x 0x1051a34b0
读尾 x 0x1051a34f4-0x4,读尾需要减四个字节,因为一个方法占四个字节,而这个0x1051a34f4地址是尾方法结束的地址,所以如果需要读尾方法的地址,就需要减掉四个字节
我们看到现在最后一位是11,换算成十进制就是17,现在我们添加touchesBegan方法,这里就变成了12.所以只要我们添加OC方法、C/C++函数、block都会添加进去。
之前我们添加了touchesBegan,现在点击下屏幕,控制台就会打印guard: 0x1053ed4e8 9 PC .D\263�,只要我们调用了方法就是调用__sanitizer_cov_trace_pc_guard.

点击屏幕时候的汇编.png

现在我们目标是找到调用的所有方法名字,我们通过void *PC = __builtin_return_address(0);打印下PC可以看到这个地址是方法返回的地址

__sanitizer_cov_trace_pc_guard返回地址.png

这个时候我们需要导入#import <dlfcn.h>

void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;  // Duplicate the guard check.
    //当前函数返回到上一个调用的地址,参数0是当前返回的地址,1是上一层方法返回的地址
  void *PC = __builtin_return_address(0);
    Dl_info info;
    dladdr(PC, &info);
    printf("fname:%s \nfbase:%p \nsname:%s \nsaddr:%p \n",info.dli_fname,info.dli_fbase,info.dli_sname,info.dli_saddr);
    
  char PcDescr[1024];
  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
···········
fname:/Users/jiangshicheng/Library/Developer/CoreSimulator/Devices/4AFEBD14-731B-416C-9454-CE8116BFFA17/data/Containers/Bundle/Application/D57CCD44-B02F-4A7D-938B-1B935AC2738F/Demo.app/Demo 
fbase:0x105b6b000 
sname:-[ViewController touchesBegan:withEvent:] 
saddr:0x105b6cec0 
guard: 0x105b714f0 9 PC .\324;  �

现在我们sname来进行操作。现在我们需要把保存到队列中,我们导入#import <libkern/OSAtomic.h>

//定义原子队列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct{
    void *pc;
    void *next;
}SYNode;

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.
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
  if (!*guard) return;  // Duplicate the guard check.
    //当前函数返回到上一个调用的地址,参数0是当前返回的地址,1是上一层方法返回的地址
  void *PC = __builtin_return_address(0);
    //创建结构体
    SYNode *node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,NULL};
    
    //加入结构
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
 
  char PcDescr[1024];
//  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    while (YES) {
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
        if (node == NULL) {
            break;
        }
        Dl_info info = {0};
        dladdr(node->pc, &info);
        printf("%s",info.dli_sname);
    }
}

这个时候就会发生死循环,这是因为循环也会调用__sanitizer_cov_trace_pc_guard,我们就需要在Other C Flags添加个func条件,变成-fsanitize-coverage=func,trace-pc-guard,现在需要添加+load方法,把if (!*guard) return;注释掉就可以了.
接下来就是取反,去重,不是OC方法的添加下划线。
最终代码

#import "ViewController.h"
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
@interface ViewController ()

@end

@implementation ViewController




- (void)viewDidLoad {
    
    [super viewDidLoad];
}

//定义原子队列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号结构体
typedef struct{
    void *pc;
    void *next;
}SYNode;

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.
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//  if (!*guard) return;  // Duplicate the guard check.
    //当前函数返回到上一个调用的地址,参数0是当前返回的地址,1是上一层方法返回的地址
  void *PC = __builtin_return_address(0);
    //创建结构体
    SYNode *node = malloc(sizeof(SYNode));
    *node = (SYNode){PC,NULL};
    
    //加入结构
    OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
 
  char PcDescr[1024];
//  printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSMutableArray<NSString *> *symbolNames = [NSMutableArray array];
    while (YES) {
        SYNode *node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
        if (node == NULL) {
            break;
        }
        Dl_info info = {0};
        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:@"dyz.order"];
    NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
    [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
}

最后

上一篇 下一篇

猜你喜欢

热点阅读