[iOS Animation]-CALayer 性能优化一

性能优化

代码应该运行的尽可能快,而不是更快 - 理查德git

在第一和第二部分,咱们了解了Core Animation提供的关于绘制和动画的一些特性。Core Animation功能和性能都很是强大,但若是你对背后的原理不清楚的话也会下降效率。让它达到最优的状态是一门艺术。在这章中,咱们将探究一些动画运行慢的缘由,以及如何去修复这些问题。github

CPU VS GPU

关于绘图和动画有两种处理的方式:CPU(中央处理器)和GPU(图形处理器)。在现代iOS设备中,都有能够运行不一样软件的可编程芯片,可是因为历史缘由,咱们能够说CPU所作的工做都在软件层面,而GPU在硬件层面。数据库

总的来讲,咱们能够用软件(使用CPU)作任何事情,可是对于图像处理,一般用硬件会更快,由于GPU使用图像对高度并行浮点运算作了优化。因为某些缘由,咱们想尽量把屏幕渲染的工做交给硬件去处理。问题在于GPU并无无限制处理性能,并且一旦资源用完的话,性能就会开始降低了(即便CPU并无彻底占用)编程

大多数动画性能优化都是关于智能利用GPU和CPU,使得它们都不会超出负荷。因而咱们首先须要知道Core Animation是如何在这两个处理器之间分配工做的。缓存

动画的舞台

Core Animation处在iOS的核心地位:应用内和应用间都会用到它。一个简单的动画可能同步显示多个app的内容,例如当在iPad上多个程序之间使用手势切换,会使得多个程序同时显示在屏幕上。在一个特定的应用中用代码实现它是没有意义的,由于在iOS中不可能实现这种效果(App都是被沙箱管理,不能访问别的视图)。性能优化

动画和屏幕上组合的图层实际上被一个单独的进程管理,而不是你的应用程序。这个进程就是所谓的渲染服务。在iOS5和以前的版本是SpringBoard进程(同时管理着iOS的主屏)。在iOS6以后的版本中叫作BackBoard服务器

当运行一段动画时候,这个过程会被四个分离的阶段被打破:网络

  • 布局 - 这是准备你的视图/图层的层级关系,以及设置图层属性(位置,背景色,边框等等)的阶段。多线程

  • 显示 - 这是图层的寄宿图片被绘制的阶段。绘制有可能涉及你的-drawRect:-drawLayer:inContext:方法的调用路径。app

  • 准备 - 这是Core Animation准备发送动画数据到渲染服务的阶段。这同时也是Core Animation将要执行一些别的事务例如解码动画过程当中将要显示的图片的时间点。

  • 提交 - 这是最后的阶段,Core Animation打包全部图层和动画属性,而后经过IPC(内部处理通讯)发送到渲染服务进行显示。

可是这些仅仅阶段仅仅发生在你的应用程序以内,在动画在屏幕上显示以前仍然有更多的工做。一旦打包的图层和动画到达渲染服务进程,他们会被反序列化来造成另外一个叫作渲染树的图层树(在第一章“图层树”中提到过)。使用这个树状结构,渲染服务对动画的每一帧作出以下工做:

  • 对全部的图层属性计算中间值,设置OpenGL几何形状(纹理化的三角形)来执行渲染

  • 在屏幕上渲染可见的三角形

因此一共有六个阶段;最后两个阶段在动画过程当中不停地重复。前五个阶段都在软件层面处理(经过CPU),只有最后一个被GPU执行。并且,你真正只能控制前两个阶段:布局和显示。Core Animation框架在内部处理剩下的事务,你也控制不了它。

这并非个问题,由于在布局和显示阶段,你能够决定哪些由CPU执行,哪些交给GPU去作。那么改如何判断呢?

GPU相关的操做

GPU为一个具体的任务作了优化:它用来采集图片和形状(三角形),运行变换,应用纹理和混合而后把它们输送到屏幕上。现代iOS设备上可编程的GPU在这些操做的执行上又很大的灵活性,可是Core Animation并无暴露出直接的接口。除非你想绕开Core Animation并编写你本身的OpenGL着色器,从根本上解决硬件加速的问题,那么剩下的全部都仍是须要在CPU的软件层面上完成。

宽泛的说,大多数CALayer的属性都是用GPU来绘制。好比若是你设置图层背景或者边框的颜色,那么这些能够经过着色的三角板实时绘制出来。若是对一个contents属性设置一张图片,而后裁剪它 - 它就会被纹理的三角形绘制出来,而不须要软件层面作任何绘制。

可是有一些事情会下降(基于GPU)图层绘制,好比:

  • 太多的几何结构 - 这发生在须要太多的三角板来作变换,以应对处理器的栅格化的时候。现代iOS设备的图形芯片能够处理几百万个三角板,因此在Core Animation中几何结构并非GPU的瓶颈所在。但因为图层在显示以前经过IPC发送到渲染服务器的时候(图层其实是由不少小物体组成的特别重量级的对象),太多的图层就会引发CPU的瓶颈。这就限制了一次展现的图层个数(见本章后续“CPU相关操做”)。

  • 重绘 - 主要由重叠的半透明图层引发。GPU的填充比率(用颜色填充像素的比率)是有限的,因此须要避免重绘(每一帧用相同的像素填充屡次)的发生。在现代iOS设备上,GPU都会应对重绘;即便是iPhone 3GS均可以处理高达2.5的重绘比率,并任然保持60帧率的渲染(这意味着你能够绘制一个半的整屏的冗余信息,而不影响性能),而且新设备能够处理更多。

  • 离屏绘制 - 这发生在当不能直接在屏幕上绘制,而且必须绘制到离屏图片的上下文中的时候。离屏绘制发生在基于CPU或者是GPU的渲染,或者是为离屏图片分配额外内存,以及切换绘制上下文,这些都会下降GPU性能。对于特定图层效果的使用,好比圆角,图层遮罩,阴影或者是图层光栅化都会强制Core Animation提早渲染图层的离屏绘制。但这不意味着你须要避免使用这些效果,只是要明白这会带来性能的负面影响。

  • 过大的图片 - 若是视图绘制超出GPU支持的2048x2048或者4096x4096尺寸的纹理,就必需要用CPU在图层每次显示以前对图片预处理,一样也会下降性能。

CPU相关的操做

大多数工做在Core Animation的CPU都发生在动画开始以前。这意味着它不会影响到帧率,因此很好,可是他会延迟动画开始的时间,让你的界面看起来会比较迟钝。

如下CPU的操做都会延迟动画的开始时间:

  • 布局计算 - 若是你的视图层级过于复杂,当视图呈现或者修改的时候,计算图层帧率就会消耗一部分时间。特别是使用iOS6的自动布局机制尤其明显,它应该是比老版的自动调整逻辑增强了CPU的工做。

  • 视图懒加载 - iOS只会当视图控制器的视图显示到屏幕上时才会加载它。这对内存使用和程序启动时间颇有好处,可是当呈现到屏幕上以前,按下按钮致使的许多工做都会不能被及时响应。好比控制器从数据库中获取数据,或者视图从一个nib文件中加载,或者涉及IO的图片显示(见后续“IO相关操做”),都会比CPU正常操做慢得多。

  • Core Graphics绘制 - 若是对视图实现了-drawRect:方法,或者CALayerDelegate-drawLayer:inContext:方法,那么在绘制任何东西以前都会产生一个巨大的性能开销。为了支持对图层内容的任意绘制,Core Animation必须建立一个内存中等大小的寄宿图片。而后一旦绘制结束以后,必须把图片数据经过IPC传到渲染服务器。在此基础上,Core Graphics绘制就会变得十分缓慢,因此在一个对性能十分挑剔的场景下这样作十分很差。

  • 解压图片 - PNG或者JPEG压缩以后的图片文件会比同质量的位图小得多。可是在图片绘制到屏幕上以前,必须把它扩展成完整的未解压的尺寸(一般等同于图片宽 x 长 x 4个字节)。为了节省内存,iOS一般直到真正绘制的时候才去解码图片(14章“图片IO”会更详细讨论)。根据你加载图片的方式,第一次对图层内容赋值的时候(直接或者间接使用UIImageView)或者把它绘制到Core Graphics中,都须要对它解压,这样的话,对于一个较大的图片,都会占用必定的时间。

当图层被成功打包,发送到渲染服务器以后,CPU仍然要作以下工做:为了显示屏幕上的图层,Core Animation必须对渲染树种的每一个可见图层经过OpenGL循环转换成纹理三角板。因为GPU并不知晓Core Animation图层的任何结构,因此必需要由CPU作这些事情。这里CPU涉及的工做和图层个数成正比,因此若是在你的层级关系中有太多的图层,就会致使CPU没一帧的渲染,即便这些事情不是你的应用程序可控的。

IO相关操做

还有一项没涉及的就是IO相关工做。上下文中的IO(输入/输出)指的是例如闪存或者网络接口的硬件访问。一些动画可能须要从山村(甚至是远程URL)来加载。一个典型的例子就是两个视图控制器之间的过渡效果,这就须要从一个nib文件或者是它的内容中懒加载,或者一个旋转的图片,可能在内存中尺寸太大,须要动态滚动来加载。

IO比内存访问更慢,因此若是动画涉及到IO,就是一个大问题。总的来讲,这就须要使用聪敏但尴尬的技术,也就是多线程,缓存和投机加载(提早加载当前不须要的资源,可是以后可能须要用到)。这些技术将会在第14章中讨论。

测量,而不是猜想

因而如今你知道有哪些点可能会影响动画性能,那该如何修复呢?好吧,其实不须要。有不少种诡计来优化动画,但若是盲目使用的话,可能会形成更多性能上的问题,而不是修复。

如何正确的测量而不是猜想这点很重要。根据性能相关的知识写出代码不一样于仓促的优化。前者很好,后者实际上就是在浪费时间。

那该如何测量呢?第一步就是确保在真实环境下测试你的程序。

真机测试,而不是模拟器

当你开始作一些性能方面的工做时,必定要在真机上测试,而不是模拟器。模拟器虽然是加快开发效率的一把利器,但它不能提供准确的真机性能参数。

模拟器运行在你的Mac上,然而Mac上的CPU每每比iOS设备要快。相反,Mac上的GPU和iOS设备的彻底不同,模拟器不得已要在软件层面(CPU)模拟设备的GPU,这意味着GPU相关的操做在模拟器上运行的更慢,尤为是使用CAEAGLLayer来写一些OpenGL的代码时候。

这就是说在模拟器上的测试出的性能会高度失真。若是动画在模拟器上运行流畅,可能在真机上十分糟糕。若是在模拟器上运行的很卡,也可能在真机上很平滑。你没法肯定。

另外一件重要的事情就是性能测试必定要用发布配置,而不是调试模式。由于当用发布环境打包的时候,编译器会引入一系列提升性能的优化,例如去掉调试符号或者移除并从新组织代码。你也能够本身作到这些,例如在发布环境禁用NSLog语句。你只关心发布性能,那才是你须要测试的点。

最后,最好在你支持的设备中性能最差的设备上测试:若是基于iOS6开发,这意味着最好在iPhone 3GS或者iPad2上测试。若是可能的话,测试不一样的设备和iOS版本,由于苹果在不一样的iOS版本和设备中作了一些改变,这也可能影响到一些性能。例如iPad3明显要在动画渲染上比iPad2慢不少,由于渲染4倍多的像素点(为了支持视网膜显示)。

保持一致的帧率

为了作到动画的平滑,你须要以60FPS(帧每秒)的速度运行,以同步屏幕刷新速率。经过基于NSTimer或者CADisplayLink的动画你能够下降到30FPS,并且效果还不错,可是没办法经过Core Animation作到这点。若是不保持60FPS的速率,就可能随机丢帧,影响到体验。

你能够在使用的过程当中明显感到有没有丢帧,但没办法经过肉眼来获得具体的数据,也无法知道你的作法有没有真的提升性能。你须要的是一系列精确的数据。

你能够在程序中用CADisplayLink来测量帧率(就像11章“基于定时器的动画”中那样),而后在屏幕上显示出来,但应用内的FPS显示并不可以彻底真实测量出Core Animation性能,由于它仅仅测出应用内的帧率。咱们知道不少动画都在应用以外发生(在渲染服务器进程中处理),但同时应用内FPS计数的确能够对某些性能问题提供参考,一旦找出一个问题的地方,你就须要获得更多精确详细的数据来定位到问题所在。苹果提供了一个强大的Instruments工具集来帮咱们作到这些。

相关文章
相关标签/搜索