前言
App中大多数的性能指标都和时间相关,如启动速度,列表滑动FPS,页面打开耗时等等。为了优化这些指标,我们需要了解时间都消耗在哪里。
通常我们会打开Time Profiler,通过聚合Call Stack来分析和优化代码耗时。偶尔会出现优化后Time Profiler已经没有什么高耗时的Call Stack,但列表滑动仍然掉帧,这时候应该怎么办呢?
不妨试试System Trace~
一个实际例子
用dyld提供的C接口来注册image加载的回调是一种常见做法,第一次注册回调的时候会立刻把当前已经加载的image回调,所以如果回调函数里有耗时操作,我们一般会在子线程注册。
模拟启动的时候注册dyld回调:
接着在真机上运行这段代码,发现在启动页面会卡很久:

为什么这里会卡很久呢?
试试Time Profiler?
就不具体讲解Time Profiler的用法了,相关资料网上很多,通过分析我们可以拿到如下的调用栈:
嗯~??不对啊,明明耗时好几秒,怎么统计出来的只有堆栈142ms。可以看到Time Profiler提供的信息有限,以下两条算是比较有用的:
- 区间选中有3.17s,但是统计到的调用栈却只有142ms
- iPhone 6一共只有两个Core,两个Core在这段时间内,大部分时候都是空闲的。
似乎线程在等待什么?
System Trace
System Trace一个比较大的优势是可以看到线程的状态,分析上面的代码(如下图),发现主线程有一段3s左右的时间被block住了,通过右侧的调用栈能看到主线程在didFinishLaunching中调用了imageNamed
,后者触发了dlopen,dlopen里在等待一个互斥锁。
切换到Events:Thread State,可以看到线程切换的每一个事件,和状态切换的发生的原因,切换后再选中主线程被block的这段时间,观察这个事件的下一个事件,因为下个事件通常是锁被释放,线程重新进入可执行的状态。
我们发现,主线程因为线程0x9e1f释放锁才进入了可执行的状态。这时候把线程0x9e1f也pin一下,观察到主线程被block这段时间,这个线程在不停的