APM - iOS 启动监控和优化

940 阅读3分钟

启动类型

  • 冷启动
  • 热启动
  • 恢复

一、启动顺序

以Main函数分隔

  • Pre-main
  • Post-main

二、启动监控

由于系统影响因素,需要我们对齐冷启动测量方式

  • dyld3相对于dyld2,添加了缓存机制,APP启动能使用到dyld缓存的情况下会快很多

  • 动态库分成了苹果自带的动态库和APP本身动态库2种,苹果系统动态库是所有APP共享,所以开机之后,其他APP启动导致动态库已经提前加载也会影响当前APP启动速度。

线上

埋点

PID

进程启动时间,该时间可以完整体现APP启动的时长

#import <sys/sysctl.h> 
#import <mach/mach.h>  

+ (BOOL)processInfoForPID:(int)pid procInfo:(struct kinfo_proc*)procInfo
{
    int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
    size_t size = sizeof(*procInfo);
    return sysctl(cmd, sizeof(cmd)/sizeof(*cmd), procInfo, &size, NULL, 0) == 0;
}

+ (NSTimeInterval)processStartTime
{
    struct kinfo_proc kProcInfo;
    if ([self processInfoForPID:[[NSProcessInfo processInfo] processIdentifier] procInfo:&kProcInfo]) {
        return kProcInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + kProcInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0;
    } else {
#if DEBUG
        NSAssert(NO, @"无法取得进程的信息");
#endif
        return 0;
    }
}
Pre-warming

由于iOS 15开始有Pre-warming的影响,会提前拉起进程,导致PID的时间提前,导致计算出来的app启动时间会变长。所以PID进程启动时间,不再能作为起始点。

Load

除了PID的时间之外,我们在代码中可以获取的最早时间点就在+load方法。但是+load方法的执行是按照一定顺序的,我们尽量需要在第一个+load方法中记录时间。

  • 调整宿主APP中Target中的OTHER_LDFLAGS

  • 或者本身启动监控组件名称首字母排在最前(默认按照组件首字母排列)

APM

apm依赖于埋点获取到数据,这边主要讲一下数据消费。

主要指标

总启动时长,pre-main时长,applicationdidfinishlaunch时长,rootviewcontroller时长

以APP版本的维度的数据变化

分位值数据,即拖尾数据

MetricKit

苹果会把过去24小时随app版本分布的启动总耗时展示出来,缺点在于数据展示不可定制。

线下

性能监控SDK

在测试阶段可以集成平台化的线下性能测试工具,收集启动耗时数据,防止APP启动性能劣化。

防劣化的同时可以通过采集函数堆栈,绘制火焰图方便获取启动中最为耗时的函数。

Xcode

// Deprecated: 由于dyld的更新,iOS 15之后无法使用

dyld在加载流程的代码中预埋了环境变量,我们可以通过添加环境变量的方式获取到pre-main内细分阶段的时间。

进入 Product > Scheme > Edit Scheme... > Run > Arguments > Environment Variables设置DYLD_PRINT_STATISTICSDYLD_PRINT_STATISTICS_DETAILS 这2个环境变量为1

设置DYLD_PRINT_STATISTICS为1,打印信息如下:

设置DYLD_PRINT_STATISTICS_DETAILS为1,打印信息如下:

Instruments - App Launch

三、启动优化

ROI

耗时类的优化,我们可以按照整体流程中各个分阶段的耗时占比来计算任务优先级。

通过埋点数据和Xcode中dyld提供的环境变量的设置,可以获取整个启动流程中各个分阶段的耗时占比。降序排列确定任务优先级。

减少库依赖

dylib loading time

动态库转静态库

动态库合并

动态库懒加载

业务启动基础组件

Application didfinishlaunching

由于需要管理application didfinishlaunching中各个组件和模块的初始化等操作,很多项目中会创建LaunchModule用于管理LaunchTask,使用多线程的方式去处理同步和异步的启动任务,启动优先级,以及启动依赖

其他

二进制重排

参考

Reducing your app’s launch time

抖音品质建设 - iOS启动优化《原理篇》

抖音品质建设 - iOS启动优化《实战篇》