iOS性能优化过程浅析

标签: General | 发表时间:2014-12-04 00:00 | 作者:ajaxj
出处:http://www.geek521.com

这一系列文章是我的读书笔记,整理一下,也算是温故而知新。

一:性能优化策略

性能问题的处理流程

  • 发现/重现问题
  • 利用工具剖析
  • 形成假设
  • 改进代码和设计

在以上的四个步骤中循环反复,直到问题解决。

Profile!不要猜!

性能优化的主要策略:

  • 不要做无用功:不要在启动时花几百ms来做logging,不要为同样的数据做多次查询
  • 试图重用:对于创建过程昂贵的对象,要重用而不是重新创建
    • Table View的cell
    • Date/Number的formatter
    • 正则表达式
    • SQLite语句
  • 使用更快的方式设计、编程:选择正确的集合对象和算法来进行编程、选择适合的数据存储格式(plist、SQLite)、优化SQLite查询语句
  • 事先做优化
    • 对于昂贵的计算,要进行事先计算。iCal中的重复事件,是预先计算出来的,并保存到数据库中。
    • 事先计算并缓存一些对象,可能会占用大量的内存。注意不要将这些对象声明为static并常驻内存。
  • 事后做优化:异步加载、懒加载
  • 为伸缩性而做优化:当数据有10条、100条、1000条甚至更多的时候,应用程序的性能不应该对应的呈数量级式的增长,否则无法使用。

说起来惭愧,我真的很少遇到性能问题。以前假设中的性能问题,很多是根本不存在的。事前计划也杜绝了不了性能问题的产生,所以不如暂时忘记它吧。当然对于一些常识性的提高性能的设计,仍然是必须的。

二:iOS应用启动速度优化

很多app的开发者都不重视app的启动速度,这对于碎片化使用情景的用户来说,简直是灾难。

iOS应用的启动速度

应用启动时,会播放一个放大的动画。iPhone上是400ms,iPad上是500ms。最理想的启动速度是,在播放完动画后,用户就可以使用。

如果应用启动过慢,用户就会放弃使用,甚至永远都不再回来。抛开代码不谈,如果抱着PC端游和单机游戏的思维,在游戏启动时强加公司Logo,启动动画,并且用户不可跳过,也会使用户的成功使用率大大降低。

iOS系统的“看门狗”

为了防止一个应用占用过多的系统资源,开发iOS的苹果工程师门设计了一个“看门狗”的机制。在不同的场景下,“看门狗”会监测应用的性能。如果超出了该场景所规定的运行时间,“看门狗”就会强制终结这个应用的进程。开发者们在crashlog里面,会看到诸如 0x8badf00d这样的错误代码(“看门狗”吃了坏的食物,它很不高兴)。

场景 “看门狗”超时时间
启动 20秒
恢复运行 10秒
悬挂进程 10秒
退出应用 6秒
后台运行 10分钟

值得注意的是,Xcode在Debug的时候,会禁止“看门狗”。

如何测试启动时间

两种方法:一种使用NSLog,另外一种使用Time Profiler。

  • 使用NSLog
     CFAbsoluteTime StartTime;  
     int main(int argc, char **argv) {  
          StartTime = CFAbsoluteTimeGetCurrent(); 
          // ...  5 }  6   
     - (void)applicationDidFinishLaunching:(UIApplication *)app {  
          dispatch_async(dispatch_get_main_queue(), ^{  
    
    NSLog(@"Launched in %f sec", CFAbsoluteTimeGetCurrent() - StartTime); 
    
    }); 
          // ... 
      }
  • 使用Time Profiler
    • Instruments->Time Profiler
    • Profile你的app
    • 切换到CPU strategy view,找到你的app启动的第一帧
    • 搜索 -[UIApplication _reportAppLaunchFinished]
    • 找到包含 -[UIApplication _reportAppLaunchFinished]的最后一帧,即可计算出启动时间

iOS App启动过程

  • 链接并加载Framework和static lib
  • UIKit初始化
  • 应用程序callback
  • 第一个Core Animation transaction

链接并加载Framework及static lib时需要注意:

  • 每个Framework都会增加启动时间和占用的内存
  • 不必要的Framework,不要链接
  • 必要的Framework,不要票房为Optional
  • 只在使用在Deployment Target之后发布的Framework时,才使用Optional(比如你的Deployment Target是iOS 3.0,需要链接StoreKit的时候)
  • 避免创建全局的C++对象

初始化UIKit时需要注意:

  • 字体、状态栏、user defaults、main nib会被初始化
  • 保持main nib尽可能的小
  • User defaults本质上是一个plist文件,保存的数据是同时被反序列化的,不要在user defaults里面保存图片等大数据

应用程序的回调:

  • application:willFinishLaunchingWithOptions:
  • 恢复应用程序的状态
  • application:didFinishLaunchingWithOptions:

我一直认为设计的本质是折衷。当你为了100ms的启动速度优化欢欣不已,而无视那长达10秒的启动动画时,应该想想究竟什么是应该做的。 做正确的事情比把事情做好更重要。

三:事件处理-拯救主线程

用户经常评论app的一个用词是“卡顿”,很大的因素是因为主线程被占用了。用户的事件是在主线程被处理的,包括点击、滚动、加速计、Proximity Sensor。

为了保证事件的平滑处理,需要进行如下优化:

  • 最小化主线程的CPU占用
  • 将工作“搬离”主线程
  • 不要阻塞主线程

最小化主线程的CPU占用

前面两篇文章,我们接触到了Time Profiler。使用它可以剖析不同线程的CPU使用情况,并给出调用堆栈的CPU时间占用百分比。如果app“卡顿”,并且在Time Profiler的结果可以找到明确的高占用堆栈,你需要把它优化掉。

将工作“搬离”主线程 – 隐式并发

为了得到更流畅的交互体验,iOS已经帮我们做了很多事情,Android就没有这么好运了。iOS将以下这些事情搬离了主线程:

  • View和layer的动画(动画绘制前的计算,而不是drawing过程)
  • Layer的组合计算(drawing后的叠加)
  • PNG的解码(是的,你没看错;而且利用了CPU的多核心)

注意滚动(Scrolling)不是一个动画,而是在Main Run Loop中不断接收事件并且处理。

将工作“搬离”主线程 – 显式并发

这里是需要开发者们搞定的部分。磁盘、网络等I/O会阻塞线程,不要把它们放到主线程里。常用的技术有:

  • Grand Central Dispatch(GCD)
  • NSOperationQueue
  • NSThread

iOS 4.0后,易用的GCD技术被广泛使用。例如:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 
^{    
 // do something in background     dispatch_async(dispatch_get_main_queue(), ^{        
 // do something on main thread    
 }); });

GCD的陷阱

GCD其实就是线程,只不过提供了一个更高层次的抽象。过多的线程一定会带来性能损失,因此GCD设计了一个最高允许的线程值(对开发者透明,不用管到底有多少)。那么如何解决这个问题呢?

  • 将队列串行化
  • 使用Dispatch sources
  • 使用带有限制的NSOperationQueue
  • 使用Cocoa Touch提供的异步方法

另外一个陷阱是线程安全:

  • UIKit必须要在主线程使用,除了UIGraphics,UIBezierPath,UIImage
  • 大多数CG、CA、Foundation的类,不是线程安全的
  • 如果你使用了ojbc runtime来进行introspection,由于它是thread safe的,可能会导致竞争

此外,iOS 4.3添加了 DISPATCH_QUEUE_PRIORITY_BACKGROUND,它拥有非常低的优先级。这个优先级只用于不太关心完成时间的真正的后台任务,如果要表示较低的优先级,你通常需要的是 DISPATCH_QUEUE_PRIORITY_LOW

不要阻塞主线程

即使占用了很少的CPU时间(如果你在Time Profiler中看到这些的数据),也可能会阻塞主线程。磁盘、网络、Lock、dispatch_sync以及向其它进程/线程发送消息都会阻塞主线 程。Time Profiler只能检测出占用CPU过多的堆栈,但检测不了这些IO的问题。

大多数的阻塞事件,都会伴随着一个系统调用,如:

  • read/write - 读写文件
  • send/recv - 收发网络数据
  • psynch_mutex_wait - 获得锁
  • mach_msg - IPC

System Trace这个Instrumentor,记录了所有的系统调用,以及每次调用的等待时间。如果你在System Trace里面发现了CPU Time很低,但Wait Time很高的调用,说明在主线程处理I/O已经严重损害了app的性能。

保证主线程的低CPU占用,将I/O移至其它线程,可以大大地提高主线程对交互事件的处理能力。我建议开发者朋友们写代码的时候,除非是以前遇到过的问题,都没有必要假设问题存在。 80%的优化都是不必要的。

相关 [ios 性能优化] 推荐:

[IOS]iOS App性能优化

- - 操作系统 - ITeye博客
iOS App的性能关注点. 虽然iPhone的机能越来越好,但是app的功能也越来越复杂,性能从来都是移动开发的核心关注点之一. 我们说一个app性能好,不是简单指感觉运行速度快,而应该是指应用启动快速、UI反馈响应及时、列表滚动操作流畅、内存使用合理,当然更不能随随便便Crash啦. 工程师开发应用时除了在设计上要避免性能“坑”的出现,在实际遇到“坑”时也要能很快定位原因所在.

iOS性能优化十三招

- - CSDN博客推荐文章
iOS应用是非常注重用户体验的,不光是要求界面设计合理美观,也要求各种UI的反应灵敏,我相信大家对那种一拖就卡卡卡的 TableView 应用没什么好印象. 还记得12306么,那个速度,相信大家都受不了. 为了提高 iOS 的运行速度,下面我将抛砖引玉介绍一些我实践过的用来提供iOS程序运行效率的方法,与大家分享,希望能得到更多的反馈和建议.

iOS性能优化过程浅析

- - 极客521 | 极客521
这一系列文章是我的读书笔记,整理一下,也算是温故而知新. 在以上的四个步骤中循环反复,直到问题解决. Profile!不要猜!. 不要做无用功:不要在启动时花几百ms来做logging,不要为同样的数据做多次查询. 试图重用:对于创建过程昂贵的对象,要重用而不是重新创建. Table View的cell.

iOS开发那些事--性能优化–内存泄露问题的解决

- - ITeye博客
内存泄漏(Memory Leaks)是当一个对象或变量在使用完成后没有释放掉,这个对象一直占有着这块内存,直到应用停止. 如果这种对象过多内存就会耗尽,其它的应用就无法运行. 这个问题在C++、C和Objective-C的MRR中是比较普遍的问题. 在Objective-C中释放对象的内存是发送release和autorelease消息,它们都是可以将引用计数减1,当为引用计数为0时候,release消息会使对象立刻释放,autorelease消息会使对象放入内存释放池中延迟释放.

「原创译文」iOS 性能优化:Instruments 工具的救命三招

- - SegmentFault 最新的文章
你的 iOS 应用,运行速度靠谱吗. 中枪的同学莫要愁,性能优化咱有妙招. 用 Xcode 自家的调试工具 Instruments,揪出那些堵线程、占内存、耗资源的问题代码,彻底破掉迷局,让应用扬眉吐气. 对于每位 iOS 开发者来说,代码性能是个避不开的话题. 随着项目的扩大和功能的增多,没经过认真调试和优化的代码,要么任性地卡顿运行,要么低调地崩溃了之……结果呢,大家用着不高兴,开发者也不开心.

MySQL性能优化

- sun - IT程序员面试网
在笔试面试中,尤其是像百度,淘宝这些数据量非常大,而且用LAMP架构的公司,数据库优化方面就显得特别重要了. 此外,除了数据库索引之外,在LAMP结果如此流行的今天,数据库(尤其是MySQL)性能优化也是海量数据处理的一个热点. 下面就结合自己的经验,聊一聊MySQL数据库优化的几个方面. 首先,在数据库设计的时候,要能够充分的利用索引带来的性能提升,至于如何建立索引,建立什么样的索引,在哪些字段上建立索引,上面已经讲的很清楚了,这里不在赘述.

Hebernate 性能优化

- - 企业架构 - ITeye博客
文章分为十三个小块儿对Hibernate性能优化技巧进行总结性分析,分析如下:. 一、在处理大数据量时,会有大量的数据缓冲保存在Session的一级缓存中,这缓存大太时会严重显示性能,所以在使用Hibernate处理大数 据量的,可以使用session. clear()或者session. evict(Object) 在处理过程中,清除全部的缓存或者清除某个对象.

Hbase 性能优化

- - CSDN博客云计算推荐文章
因 官方Book Performance Tuning部分章节没有按配置项进行索引,不能达到快速查阅的效果. 所以我以配置项驱动,重新整理了原文,并补充一些自己的理解,如有错误,欢迎指正. 默认值:3分钟(180000ms). 说明:RegionServer与Zookeeper间的连接超时时间.

JavaScript性能优化

- - ITeye博客
互联网泡沫让投资者长了记性:态度更加谨慎.         如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍JavaScript性能优化的技巧,并提供相应的测试用例,供大家在自己使用的浏览器上验证, 同时会对特定的JavaScript背景知识做一定的介绍.

Mysql性能优化

- - 数据库 - ITeye博客
MySQL性能优化.   性能优化是通过某些有效的方法来提高MySQL的运行速度,减少占用的磁盘空间. 性能优化包含很多方面,例如优化查询速度,优化更新速度和优化MySQL服务器等.   数据库管理人员可以使用SHOW STATUS语句来查询MySQL数据库的性能. 语法:SHOW STATUE LIKE ‘value’;其中value参数是常用的几个统计参数.