画面的生成到展示需要经过CPU处理和GPU处理两个过程,其中CPU主要负责软件层面的工作,GPU主要负责硬件层面的工作。这中间的过程大致可以分为六个步骤:
布局 - 准备视图/图层层级,设置图层属性。
显示 - 这是图层的寄宿图片被绘制的阶段。绘制有可能涉及你的-drawRect:
和-drawLayer:inContext:
方法的调用路径。
准备 - 这是Core Animation准备发送动画数据到渲染服务的阶段。这同时也是Core Animation将要执行一些别的事务例如解码动画过程中将要显示的图片的时间点。
提交 - 这是最后的阶段,Core Animation打包所有图层和动画属性,然后通过IPC(内部处理通信)发送到渲染服务进行显示。
对所有的图层属性计算中间值,设置OpenGL几何形状(纹理化的三角形)来执行渲染
在屏幕上渲染可见的三角形
前五个阶段都在软件层面处理(通过CPU),只有最后一个被GPU执行。我们真正只能控制前两个阶段:布局和显示。Core Animation框架在内部处理剩下的事务,你也控制不了它。但是在布局和显示阶段,你可以决定哪些由CPU执行,哪些交给GPU去做。
这方面的细节不再过多介绍,苹果的官方文档有详细的阐述,感兴趣的同学可以看这个中文译文。
为了做到动画的平滑,一般需要以60帧每秒的速度。所以每帧画面的处理过程只有16.6ms,如果某帧处理时间超出,则可能影响到后续帧的展示,造成丢帧,丢帧过多时会造成肉眼可见的卡顿。
现在主流的卡顿监控方案主要有两种:
FPS监控:通过计算主线程一段时间内的RunLoop调用次数衡量当前页面绘制的质量
主线程卡顿监控: 通过开辟一个子线程监控主线程RunLoop状态区域的耗时,检测是否发生卡顿
YYFPSLabel用到了第一种实现方案,微信读书和美团结合使用了这两种方案。
这两种方案都集中于监控主线程runloop的状态,然而在实际的测试中,我发现有时候出现明显的卡顿时,主线程的指标反馈良好。
网上有人详细阐述了这个现象,详见链接。
正如我们在上一节所说,画面的展示不仅和CPU相关,还和GPU有很大的关系。所以,如果能对GPU的性能也做到监控,能更好的反映APP的当前性能。
对于CPU的监控,可以简单的创建一个CADisplayLink,计算一秒内的调用次数得到FPS数据,实现效果如下图:
PS: demo通过改造KMCGeigerCounter实现,测试设备为iPad4,系统iOS10.3.3,选用此设备的原因在于iPhone设备在本文后续一些测试中不会出现掉帧的现象,难以进行对比。在本次测试中,滑动有细微的卡顿,但是由于经过了录屏和转换gif两个过程,gif图已很难辨别出卡顿现象。
对于GPU的监控,KMCGeigerCounter提供了一种思路,引入SKView,并监控刷新频率来反馈GPU性能指标,然而实际测试效果并不理想。
受这种思路的启发,我尝试通过计算GLKView中draw call的调用次数,监测GPU的帧率,实现后测试效果如下:
很明显可以注意到GPU的帧率降低到了12帧左右,但是CPU的帧率同时也相应降低到了12帧!这种方案虽然能一定程度反映GPU性能指标,但是却无法将CPU的性能和GPU的性能指标分离。我推测造成这个问题的原因是GPU的draw call调用阻塞了当前runloop。普通的OpenGL调用不应该造成CPU和GPU之间的同步,但是一些OpenGL的操作会造成CPU和GPU之间同步现象的出现,stackOverflow上有人讨论过这个问题。
分离CPU和GPU的性能指标有两种思路:
可以通过开关关闭对GPU的监控,然而这样操作相对较麻烦
尝试将GPU的draw call放在背景线程的runloop中调用
由于GLKView是UIView的子类,在背景线程调用GLKView的方法,可能会造成不可预期的影响。或许可以考虑使用图层的方式渲染OpenGL,来规避这个问题。CAEAGLLayer看起来是一个好的解决方案。
实现后效果如下:
可以看到CPU和GPU的性能指标如预期一般成功分离了。
https://github.com/kconner/KMCGeigerCounter
https://wereadteam.github.io/2016/05/03/WeRead-Performance/
https://www.jianshu.com/p/86705c95c224
http://www.tanhao.me/code/151113.html/
http://www.cocoachina.com/ios/20170629/19680.html
https://github.com/yehot/YYFPSLabel
https://stackoverflow.com/questions/29551516/glclear-takes-too-long-android-opengl-es-2
本文来自网易实践者社区,经作者彭衍授权发布。