Instruments使用技巧html
关于Instruments官方有一个颇有用的用户使用Guide,固然若是不习惯官方英文能够在这里找到中文本翻译版本PDF参阅.Instruments 确实是一个很强大的工具,用它来收集关于一个或多个系统进程的性能和行为的数据极为方便,并能及时跟踪随着时间产生的数据.还能够普遍收集不一样类型的数据.关于Instrument工具基本使用不在赘述.以下重点说明一些使用技巧.ios
1.概览编程
工具经过Xcode工具栏中Product->Profile能够启动,启动后界面以下:缓存
Instrument概览[via by chenkai]性能优化
当点击Time Profiler应用程序开始运行后.就能获取到整个应用程序运行消耗时间分布和百分比.为了保证数据分析在统一使用场景真实行有以下点须要注意:网络
在开始进行应用程序性能分析的时候,必定要使用真机,模拟器运行在Mac上,然而Mac上的CPU每每比iOS设备要快。相反,Mac上的GPU和iOS设备的彻底不同,模拟器不得已要在软件层面(CPU)模拟设备的GPU,这意味着GPU相关的操做在模拟器上运行的更慢,尤为是使用CAEAGLLayer来写一些OpenGL的代码时候. 这就致使模拟器性能数据和用户真机使用性能数据相去甚运.多线程
另外在开始性能分析前另一件重要的事情是,应用程序运行必定要发布配置 而不是调试配置.app
在发布环境打包的时候,编译器会引入一系列提升性能的优化,例如去掉调试符号或者移除并从新组织代码.另iOS引入一种"Watch Dog"[看门狗]机制.不一样的场景下,“看门狗”会监测应用的性能。若是超出了该场景所规定的运行时间,“看门狗”就会强制终结这个应用的进程.开发者能够crashlog看到对应的日志.但Xcode在调试配置下会禁用"Watch Dog".框架
2.Time Profileride
选择Time Profiler启动.
time profile时间分析工具用来检测应用CPU的使用状况.能够看到应用程序中各个方法正在消耗CPU时间.使用大量CPU不必定是个问题.相似咱们客户端中不一样场景的天气动画[相似大雨]的路径就对CPU依赖就很是高,动画自己也是很是苛刻且耗费资源较多的任务.
点击Record 开始运行.
Time Profile 分析界面[via by chenkai]
刚开始咱们拿到分析数据时每每是这样的:
性能数据[via by chenkai]
这里显示的是执行代码完整路径,其中系统和应用自己一些调用路径彻底揉捏在一块儿.彻底看不到咱们关心的应用程序中实际代码执行耗时和代码路径实际所在位置.简单的方式能够快速勾选右边Call Tree中Separate Thread和Hide System Libraries两个选项[后面会解释选项做用]:
拆分后性能数据[via by chenkai]
能够看到直接可以看到应用程序各个方法调用耗时直接路径,剔除掉了系统相关方法和反向调用树路径.清爽不少.若是以为这还不够直观,选择任意一个耗时方法分支[这里选择WeatherViewController viewDidLoad]双击进入会看到:
代码&耗时详情
能够直接定位到viewDidLoad的代码,也能够直观的看到改方法下调用其余方法耗时的时间.相似[self loadCityWeatherScroollerView]耗时是121x,x既耗时单位这里为ms毫秒.固然若是直接在Instrument找到问题以为不方便修改,能够直接点击右上方Xcode按钮会直接定位Xcode对应调用方法入口.这样很容易可以快速定位代码占用CPU最多的方法.也能够打开Xcode快速修改并从新运行Profile来看修改后耗时先后对比.简单便捷.
这里对右侧call tree选项有必要作一下说明[官方user guide翻译]:
Separate By Thread:线程分离,只有这样才能在调用路径中可以清晰看到占用CPU最大的线程.
Invert Call Tree:从上到下跟踪堆栈信息.这个选项能够快捷的看到方法调用路径最深方法占用CPU耗时,好比FuncA{FunB{FunC}},勾选后堆栈以C->B->A把调用层级最深的C显示最外面.
Hide Missing Symbols:若是dSYM没法找到你的APP或者调用系统框架的话,那么表中将看到调用方法名只能看到16进制的数值,勾选这个选项则能够隐藏这些符号,便于简化分析数据.
Hide System Libraries:这个就更有用了,勾选后耗时调用路径只会显示app耗时的代码,性能分析广泛咱们都比较关系本身代码的耗时而不是系统的.基本是必选项.注意有些代码耗时也会归入系统层级,能够进行勾选先后先后对执行路径进行比对会很是有用.
关于其余方法再也不赘述.
性能分析&代码优化
咱们此次性能优化主要针对以下两个使用场景:
A:应用程序第一次启动到进入天气首页的时间.
B:从后台切到前台天气首页占用时间.
在尚未拿到性能分析数据以前,一直认为第一次启动耗时主要浪费AppDelegate中第三方框架初始化上[相似WeiBo&WeChat 相关SDK初始化调用].当咱们拿到实际性能数据耗时占用比时发现实际状况并不是如此:
启动耗时
如上能够看到应用程序启动初始化工做主要会在MJAppDelegate以下两个方法展开:willFinishLaunchingWithOptions和didFinishLaunchingWithOptions,其中第三方框架初始化工做主要是willFinishLaunchingWithOptions中完成的.而实际状况耗时占比很是小.基本能够忽略不计.
而咱们要优化两个启动时间场景,不一样在于.第一次进入应用须要通过新手教程、添加城市、请求城市数据、解析数据、初始化天气首页UI元素并加载场景动画. 而从后台进入时则从本地存储DT文件中解析天气数据、初始化天气首页UI元素并加载天气动画.
1.NSDateFormatter问题凸显
针对这点重点分析应用启动&天气首页耗时. 在AB两个场景均发现加载首页元素发现以下问题:
NSDate(TimeAgo)getDateStrByTimeZone耗时
继续跟踪发现:
NSDate耗时
在AB两个场景里均出现加载MJLineChartView 和 TendencyChartView 时获取时区对应时间上耗时较大.而耗时主要在getDateStrByTimeZone这个方法调用上.
getDateStrByTimeZone方法
其中建立一个NSDateFormatter对象平均耗时33ms左右 而设置NSDateFormatter的3个属性平均耗时也在30ms左右.由于首页24小时天气和将来几天预报中.须要for循环中遍历数据,致使这个方法别重复调用屡次,则消耗时间不断叠加.
针对这个问题:
NSDateFormatter对象自己初始化很慢,一样还有NSCalendar也是如此.然而在一些使用场景中不可避免要使用他们,好比Json数据解析中.使用这个对象同时避免其性能开销带来性能开销,通常比较好的方式是经过添加属性(推荐)或建立静态变量保持该对象只被初始化一次,而被屡次复用.不得不值得一提的是设置一个NSDateFormatter属性速度差很少是和建立新的实例对象同样慢!
添加属性方式以下:
属性方式
针对NSDateFormatter时间开销出了重用对象外,尽可能避免采用其处理多个日期格式.固然针对日期格式处理若是须要提升更多速度,能够直接采用C,能够采用第三方库来规避这个问题..
2.UIImage缓存取舍
在项目代码中看到大量使用以下代码:
UIImage使用
在Main Thread中发现不一样动画场景中Image IO 开销和耗时所占比例均不一,在UIImage元素较多整体叠加耗时也会占用必定比例.内存开销也会明显增高.
UIImage加载图片方式通常有两种:
A:imagedNamed初始化
B:imageWithContentsOfFile初始化
两者不一样之处在于,imageNamed默认加载图片成功后会内存中缓存图片,这个方法用一个指定的名字在系统缓存中查找并返回一个图片对象.若是缓存中没有找到相应的图片对象,则从指定地方加载图片而后缓存对象,并返回这个图片对象.
而imageWithContentsOfFile则仅只加载图片,不缓存.
大量使用imageNamed方式会在不须要缓存的地方额外增长开销CPU的时间来作这件事.当应用程序须要加载一张比较大的图片而且使用一次性,那么实际上是没有必要去缓存这个图片的,用imageWithContentsOfFile是最为经济的方式,这样不会由于UIImage元素较多状况下,CPU会被逐个分散在没必要要缓存上浪费过多时间.
使用场景须要编程时,应该根据实际应用场景加以区分,UIimage虽小,但使用元素较多问题会有所凸显.
3.天气首页加载策略
在AB两种场景把性能数据对比分析发现:
天气首页WeatherView更新耗时
天气首页WeatherView初始化耗时一直300ms-450ms之间,占据首页耗时很大一部分.且一直固定的开销.占据Main Thread3分之一.
而用户进入最早看到是天气首页上半部分:
上半部分
而下半部分须要滚动才能看到下半部分.且不必定触发:
下半部分
而如今整个首页View的初始化和更新所有放到主线程来作.其中WeatherInfoView updateAllInfo方法更新耗时最长.更多的view意味着更多的渲染,也就是意味更多的CPU和内存消耗,对于咱们天气首页在UIScrollView里边嵌套了不少view更是如此。
而针对这种状况不要在主线程承载过多的操做.uikit渲染,用户输入回应都须要主进程上完成.主线程被意外block或者加载响应耗时过多都会影响到用户体验.而针对资源消耗过大操做,处理原则是最小化主线程的CPU占用,将工做“搬离”主线程, 不要阻塞主线程.相似本地一些IO彻底移到其余线程来作.
调试time profiler过程当中发现,即便占用了不多的CPU时间(若是你在Time Profiler中看到这些的数据),也可能会阻塞主线程。磁盘、网络、Lock、dispatch_sync以及向其它进程/线程发送消息都会阻塞主线 程。Time Profiler只能检测出占用CPU过多的堆栈,但检测不了这些IO的问题.很奇怪.在System Trace里面忽然发现了CPU Time很低,但Wait Time很高的调用,说明在主线程处理I/O已经严重损害了app的性能,这个时候考虑把这个操做优化了.
而针对咱们应用首页ui中多个view,在加载策略彻底能够采用多线程进行同步加载,只把上半部分放在主线程中加载,下班能够同时开一个线程进行同步加载.这样能够大大下降组线程初始化和更新时间,当首页初始化完毕已经呈现是,下半部分其实已经另一个线程处理完毕.
另外针对单个view 尽可能不要在viewWillAppear费时的操做,viewWillAppear在 view 显示以前被调用,出于效率考虑,在这个方法中不要处理复杂费时的事情;只应该在这个方法设置 view 的显示属性之类的简单事情,好比背景色,字体等。否则,用户会明显感受到 view 显示迟钝.
4:应用首次加载时间
应用首次启动加载操做:
首次加载
首次加载坐了以下操做:
A: 连接和载入:能够在Time Profile中显示dyld载入库函数,库会被映射到地址空间,同时完成绑定以及静态初始化.
B: UIKit初始化:若是应用的Root View Controller是由XIB实现的,也会在启动时被初始化.
C: 应用回调:调用UIApplicationDeleagte的回调:application:didFinishLaunchingWithOptions.
D: 第一次Core Animation调用:在启动后的方法-[UIApplication _resportAppLaunchFinished]中调用CA::Transaction::commit实现第一帧画面的绘制.
应用程序首次加载中启动方法willFinishLaunchingWithOptions和didFinishLaunchingWithOptions只作应用程序首次启动必须的要操做,而针对_dyid_start在初始化库framework函数的操做.没必要要的Framework不要连接,避免首次加载耗时.