iOS相关技术实现iOS 的那些事儿

性能分析工具——os_signpost

2019-07-03  本文已影响0人  小虾米1309

1. os_signpost是什么

signpost这个单词是路标、指示牌的意思,比如距离北京还有158km。顾名思义,os_signpost就是在代码里面插入一些标记,作用相当于路标。官方文档是这样描述的:

The os_signpost APIs let clients add lightweight instrumentation to
code for collection and visualization by performance analysis tooling.

用人话说就是:os_signpost是一个轻量级的可视化的性能分析工具。

2. os_signpost的提出背景

代码层面的性能分析,最直观的方式就是标识出一段代码的开始和结尾,然后计算下耗时。就像下面这样:

CFTimeInterval begin = CACurrentMediaTime();
// do something 
CFTimeInterval end = CACurrentMediaTime();
NSLog(@"cost = %@",(end - begin));

然而如果代码逻辑复杂、有先后关系、存在多个线程等,单靠某小段代码的标记,就不是那么直观了。

于是,在2018年9月,苹果推出了os_signpost,它可以配合Instruments,显示可视化的效果,WWDC视频Measuring Performance Using Logging有介绍。因为是18年才推出,所以os_signpost只支持iOS12及以上系统,Xcode10及更高版本。

3. os_signpost的用法

这里以OC代码为例。

(1) 先导入头文件,为了使用方便,再定义两个宏:
#include <os/signpost.h>

#define INNER_BEGIN_LOG(subsystem, category, name) \
os_log_t m_log_##name = os_log_create((#subsystem), (#category));\
os_signpost_id_t m_spid_##name = os_signpost_id_generate(m_log_##name);\
os_signpost_interval_begin(m_log_##name, m_spid_##name, (#name));

#define INNER_END_LOG(name) \
os_signpost_interval_end(m_log_##name, m_spid_##name, (#name));

这两个宏是成对使用的,这段代码可以放公共的头文件里,方便不同的地方用。

(2)给代码插入路标:

页面初始化的时候插入名字为init的标记,
viewDidAppear的时候插入名字为viewDidAppear的标记,
接口请求回调时插入名字为requestDidCompleted的标记,
数据处理完插入名字为requestProcessComplete的标记。

- (id)init {
    self = [super init];
    if (self) {
        if (@available(iOS 12.0, *)) {
            INNER_BEGIN_LOG(fourPage, init, init);
            INNER_END_LOG(init);
        }
        // do something
    }
    return self;
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    // do something
    if (@available(iOS 12.0, *)) {
        INNER_BEGIN_LOG(fourPage, init, viewDidAppear);
        INNER_END_LOG(viewDidAppear);
    }
}

- (void)requestDidCompleted {
    if (@available(iOS 12.0, *)) {
        INNER_BEGIN_LOG(fourPage, init, requestDidCompleted);
        INNER_END_LOG(requestDidCompleted);
    }
    // do something
    if (@available(iOS 12.0, *)) {
        INNER_BEGIN_LOG(fourPage, init, requestProcessComplete);
        INNER_END_LOG(requestProcessComplete);
    }
}
(3)工程设置

Debug Information Format设置为DWARF with dSYM File,如下图所示:

工程设置.png
这一步是为了后面在Instruments里面能直接看到代码。
现在就可以插上手机跑起来了。
(4)配合Instruments
打开Instruments的Time Profiler,默认会有一行Points of Interest,这里用不到,可以把它删了,选中再按删除键即可。然后添加os_signpost,如下图所示: 添加os_signpost.png

把这个模板保存起来,点上面的File->Save As Template...,输入名字signpost。

下次就可以直接从Instruments里打开这个自定义模板了: 自定义模板.png
选择刚刚跑的app,点左上角的红色按钮开始录制,这时候界面显示Recording,操作完了再次点左上角按钮结束录制,然后进入下面这样的界面: Instruments_1.png
刚录制完可能看不到刚刚插入的那些标记,只要在上图中红线位置往下拖,把界面展开就看到了,如下图所示。左边红框就是添加的4个标记,右边红框里的竖线是相应的时间。把鼠标放在右边红框位置,滚动滚轮,可以缩放时间轴,笔记本用户可以用触摸板缩放。
Instruments_2.png

时间轴放大后可以清晰地看到,从init到viewDidApper整个过程耗时840ms,其中接口请求耗时405ms,接口解析耗时35ms,页面渲染耗时400ms,还可以看到这段时间主线程压力很大。

到这就可以分析一下具体哪些代码耗时了。在下图1位置选Samples,就是对代码采样,在2位置消耗CPU较严重的区域点击,然后在3位置就可以看到该位置对应的代码,如果前面Debug Information Format没有设置的话,这里是看不到代码的。 Instruments_3.png
以我的代码为例,可以计算出onlineChatBtn加载图片用了48ms,而它的父视图初始化用了305ms,这些都是在主线程执行的。

4. 小结

通过以上这些操作,我们能够看到两个标记之间具体执行了哪些代码,以及哪些代码比较耗时,进而可以有针对性地进行优化。

上一篇下一篇

猜你喜欢

热点阅读