WWDC 学习笔记。
developer.apple.com/videos/play…
尾调用优化对 Time Profiler 的影响
KRIS 知道自己的 App 做了什么,它的源码是这样的:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [[UIColor purpleColor] CGColor]);
CGContextAddPath(context, self.path);
CGContextDrawPath(context, kCGPathStroke);
}
Time Profiler 中显示CGContextDrawPath是占用时间最长的方法,但是在 Time Profiler 中显示的 CGContextDrawPath 的调用栈与预期的不符。
原因是编译器对 CGContextDrawPath 进行了尾调用优化,导致 Time Profiler 在获取样本的时候没能找到正确的调用栈。
解决这个问题一般有两种做法:
- 修改编译选项,去掉这个优化。但这样做会影响性能
- 查看反汇编代码,如果调用的时候使用了
bl指令,则是普通调用。否则就是尾调用优化。
KRIS 使用反汇编确认了编译器是对 CGContextDrawPath 方法进行了尾调用优化。解除了疑问。所以可以确认是 drawRect: 影响了程序的性能。
多线程真的就更快吗?
KRIS 找到了一个异步 drawsAsynchrously 的方法,一顿操作之后证明了一个问题:多线程并不意味着更快,需要确定它是否真的在做事情。因为多条线程它们可能是在交替做事,而并不是在同时做事。
有两种做法来进行验证:
- 将 Time Profiler 切换到 CPU Strategy。此时 Time Line 显示的是各个 CPU 工作的一个平均情况。对 Time Line 进行放大,则会看到一个确切的值,CPU 是否在工作。就可以确切地查看 CPU 是交替运行还是同时运行的。
- 将 Time Profiler 切换到 Thread Strategy。Time Line 上显示的每个图标代表一个 Time Profiler 拿到的样本,点击它可以看到调用栈。通着这个 Time Line,可以查看线程是在交替运行还是同时运行的。
如果是同时运行的话,才能对程序起到优化作用。否则的话还可能会拖慢我们的程序,因为除了处理原来的事情,还需要管理线程。
Time Profiler 中占用比例大的方法就耗时吗?
并不是。占用的比例大,只是说 Time Profiler 在进行快照的时候看家它的次数多。一个非常短的方法,也有可能因为被频繁地调用而在 Time Profiler 显示出占了极大的比例。
Time Profiler 不知道某个确切的被调用的方法花费了多少时间、调用多少次!
KRIS 在代码中添加了 NSLog,发现他调用了这个方法 10000 次,绘制了 10000 根线。
所以 Time Profiler 在拿快照的时候就看见它的次数比较多,所有它占用的比例就大。
实际上他只需要 100 根线。KRIS 做的第一个有用的优化就是基于此。(具体是怎么优化的没看明白,应该就是减少了次数)
耗时的 objc_msgSend
KRIS 在 Detail Panel 中发现,他有一个 buildPath 的方法占用了 55%,这是他的下一个瓶颈。
他点击了该方法后边的向右箭头➡️,进入了一个新的面板。
然后发现,他花费了 10% 的时间在 objc_msgSend 方法上。然后他双击方法,直接跳转到了源码,并且源码的每一行后都显示出了该行花费的时间。他就是在调用这个方法的时候产生的 objc_msgSend 花费了 10% 的时间:
[iterator getNextElement: &modelElement]
但是 objc_msgSend 是一个经过极致优化的超级快的方法,它也不需要 push 栈。一般在 Time Profiler 中也看不见它。那为它能占用 10% 呢?
情况是这样的:KRIS 在 buildPath 中有一个 while 循环,调用了非常非常多次 getNextElement,而它 buildPath 方法本事又十分精简。这种情况下,objc_msgSend 的时间开销就达到了一个可衡量的程度。
KRIS 又叫了另一个人上来讲了一大堆。最后他们使用 Swift 对这个 ViewController 重写,利用 Swift 的特性对这段代码进行了内联,避免了 objc_msgSend 的开销,使 CUP 的使用率降低了 6%。
总结
- "当你想要分析你的程序把大部分时间都花在哪的时候,就可以使用 Timer Profiler。"
- "要深入探索,你第一眼看到一个问题的时候可能觉得它无法解决,你或者会说别人写出来也是这个样子的,或者说系统的特性就是这样。其实整个世界的细节 Time Profiler 都已经呈现给你了,使用它你你可以向我们今天这样解决性能问题。"