iOS 性能优化总结
卡顿产生的原因
在 VSync
信号到来后,系统图形服务会通过 CADisplayLink
等机制通知 App
,App
主线程开始在 CPU
中计算显示内容,比如视图的创建、布局计算、图片解码、文本绘制等。随后 CPU
会将计算好的内容提交到 GPU
去,由 GPU
进行变换、合成、渲染。随后 GPU
会把渲染结果提交到帧缓冲区去,等待下一次 VSync
信号到来时显示到屏幕上。由于垂直同步的机制,如果在一个 VSync
时间内,CPU
或者 GPU
没有完成内容提交,则那一帧就会被丢弃,等待下一次机会再显示,而这时显示屏会保留之前的内容不变。这就是界面卡顿的原因。
在开发中,CPU
和GPU
中任何一个压力过大,都会导致掉帧现象,所以在开发时,也需要分别对CPU
和GPU
压力进行评估和优化。
iOS 设备中的 CPU & GPU
CPU
加载资源,对象创建,对象调整,对象销毁,布局计算,Autolayout,文本计算,文本渲染,图片的解码, 图像的绘制(Core Graphics)都是在CPU
上面进行的。
GPU
GPU
是一个专门为图形高并发计算而量身定做的处理单元,比CPU
使用更少的电来完成工作并且GPU
的浮点计算能力要超出CPU
很多。
GPU
的渲染性能要比CPU
高效很多,同时对系统的负载和消耗也更低一些,所以在开发中,我们应该尽量让CPU
负责主线程的UI
调动,把图形显示相关的工作交给GPU
来处理,当涉及到光栅化等一些工作时,CPU
也会参与进来,这点在后面再详细描述。
相对于CPU
来说,GPU
能干的事情比较单一:接收提交的纹理(Texture)和顶点描述(三角形),应用变换(transform)、混合(合成)并渲染,然后输出到屏幕上。通常你所能看到的内容,主要也就是纹理(图片)和形状(三角模拟的矢量图形)两类。
CPU 和 GPU 的协作
由上图可知,要在屏幕上显示视图,需要CPU
和GPU
一起协作,CPU
计算好显示的内容提交到GPU
,GPU
渲染完成后将结果放到帧缓存区,随后视频控制器会按照 VSync
信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
缓冲机制
iOS
使用的是双缓冲机制。即GPU
会预先渲染好一帧放入一个缓冲区内(前帧缓存),让视频控制器读取,当下一帧渲染好后,GPU
会直接把视频控制器的指针指向第二个缓冲器(后帧缓存)。当你视频控制器已经读完一帧,准备读下一帧的时候,GPU
会等待显示器的VSync
信号发出后,前帧缓存和后帧缓存会瞬间切换,后帧缓存会变成新的前帧缓存,同时旧的前帧缓存会变成新的后帧缓存。
优化方案
在YY
大神的 iOS 保持界面流畅的技巧中详细介绍了 CPU 资源消耗原因和解决方案和 GPU 资源消耗原因和解决方案,这里面包括了开发中的大部分场景,可以帮助我们快速定位卡顿的原因,迅速解决卡顿。
下面是一些常见的优化方案!
TableViewCell 复用
在cellForRowAtIndexPath:
回调的时候只创建实例,快速返回cell
,不绑定数据。在willDisplayCell: forRowAtIndexPath:
的时候绑定数据(赋值)。
高度缓存
在tableView
滑动时,会不断调用heightForRowAtIndexPath:
,当 cell
高度需要自适应时,每次回调都要计算高度,会导致 UI 卡顿。为了避免重复无意义的计算,需要缓存高度。
怎么缓存?
- 字典,NSCache。
- UITableView-FDTemplateLayoutCell
视图层级优化
不要动态创建视图
- 在内存可控的前提下,缓存
subview
。 - 善用
hidden
。
减少视图层级
- 减少
subviews
个数,用layer
绘制元素。 - 少用
clearColor
,maskToBounds
,阴影效果等。
减少多余的绘制操作
图片
- 不要用
JPEG
的图片,应当使用PNG
图片。 - 子线程预解码(
Decode
),主线程直接渲染。因为当image
没有Decode
,直接赋值给imageView
会进行一个Decode
操作。 - 优化图片大小,尽量不要动态缩放(
contentMode
)。 - 尽可能将多张图片合成为一张进行显示。
减少透明 view
使用透明view
会引起blending
,在iOS
的图形处理中,blending
主要指的是混合像素颜色的计算。最直观的例子就是,我们把两个图层叠加在一起,如果第一个图层的透明的,则最终像素的颜色计算需要将第二个图层也考虑进来。这一过程即为Blending
。
会导致blending
的原因:
UIView
的alpha
<1
。UIImageView
的image
含有alpha channel
(即使UIImageView
的alpha
是1
,但只要image
含有透明通道,则仍会导致blending
)。
为什么blending
会导致性能的损失?
原因是很直观的,如果一个图层是不透明的,则系统直接显示该图层的颜色即可。而如果图层是透明的,则会引起更多的计算,因为需要把另一个的图层也包括进来,进行混合后的颜色计算。
opaque
设置为YES
,减少性能消耗,因为GPU
将不会做任何合成,而是简单从这个层拷贝。
减少离屏渲染
离屏渲染指的是在图像在绘制到当前屏幕前,需要先进行一次渲染,之后才绘制到当前屏幕。
OpenGL
中,GPU
屏幕渲染有以下两种方式:
On-Screen Rendering
即当前屏幕渲染,指的是GPU
的渲染操作是在当前用于显示的屏幕缓冲区中进行。Off-Screen Rendering
即离屏渲染,指的是GPU
在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
为什么离屏渲染会发生卡顿?主要包括两方面内容:
- 创建新的缓冲区。
- 上下文切换,离屏渲染的整个过程,需要多次切换上下文环境(
CPU
渲染和GPU
切换),先是从当前屏幕(On-Screen)切换到离屏(Off-Screen);等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕。而上下文环境的切换是要付出很大代价的。
设置了以下属性时,都会触发离屏渲染:
layer.shouldRasterize
,光栅化layer.mask
,遮罩layer.allowsGroupOpacity
为YES
,layer.opacity
的值小于1.0
layer.cornerRadius
,并且设置layer.masksToBounds
为YES
。可以使用剪切过的图片,或者使用layer
画来解决。layer.shadows
,(表示相关的shadow开头的属性),使用shadowPath
代替。两种不同方式来绘制阴影: 不使用
shadowPath
使用
shadowPath
性能差别,如下图:
离屏渲染的优化建议
- 使用
ShadowPath
指定layer
阴影效果路径。 - 使用异步进行
layer
渲染(Facebook
开源的异步绘制框架AsyncDisplayKit
)。 - 设置
layer
的opaque
值为YES
,减少复杂图层合成。 - 尽量使用不包含透明(
alpha
)通道的图片资源。 - 尽量设置
layer
的大小值为整形值。 - 直接让美工把图片切成圆角进行显示,这是效率最高的一种方案。
- 很多情况下用户上传图片进行显示,可以在客户端处理圆角。
- 使用代码手动生成圆角
image
设置到要显示的View
上,利用UIBezierPath
(Core Graphics
框架)画出来圆角图片。
合理使用光栅化 shouldRasterize
光栅化是把GPU
的操作转到CPU
上,生成位图缓存,直接读取复用。
优点:
CALayer
会被光栅化为bitmap
,shadows
、cornerRadius
等效果会被缓存。
缺点:
- 更新已经光栅化的
layer
,会造成离屏渲染。 bitmap
超过100ms
没有使用就会移除。- 受系统限制,缓存的大小为 2.5X Screen Size。
shouldRasterize
适合静态页面显示,动态页面会增加开销。如果设置了shouldRasterize
为 YES
,那也要记住设置rasterizationScale
为contentsScale
。
异步渲染
在子线程绘制,主线程渲染。例如 VVeboTableViewDemo
理性使用-drawRect:
大家或许感到奇怪,有不少开发者在发有关性能优化的博客当中指出使用-drawRect:
来优化性能。但是我这里不太建议大家未经思考的使用-drawRect:
方法。原因如下:
当你使用UIImageView
在加载一个视图的时候,这个视图虽然依然有CALayer
,但是却没有申请到一个后备的存储,取而代之的是使用一个使用屏幕外渲染,将CGImageRef
作为内容,并用渲染服务将图片数据绘制到帧的缓冲区,就是显示到屏幕上,当我们滚动视图的时候,这个视图将会重新加载,浪费性能。所以对于使用-drawRect:
方法,更倾向于使用CALayer
来绘制图层。因为使用CALayer
的-drawInContext:
,Core Animation
将会为这个图层申请一个后备存储,用来保存那些方法绘制进来的位图。那些方法内的代码将会运行在 CPU
上,结果将会被上传到GPU
。这样做的性能更为好些。
静态界面建议使用-drawRect:
的方式,动态页面不建议。
按需加载
- 局部刷新,刷新一个
cell
就能解决的,坚决不刷新整个section
或者整个tableView
,刷新最小单元元素。 - 利用
runloop
提高滑动流畅性,在滑动停止的时候再加载内容,像那种一闪而过的(快速滑动),就没有必要加载,可以使用默认的占位符填充内容。
关于性能测试
在出现图像性能问题,滑动,动画不够流畅之后,我们首先要做的就是定位出问题的所在。而这个过程并不是只靠经验和穷举法探索,我们应该用有脉络,有顺序的科学的手段进行探索。
首先,我们要有一个定位问题的模式。我们可以按照这样的顺序来逐步定位,发现问题。
- 定位帧率,为了给用户流畅的感受,我们需要保持帧率在
60
帧左右。当遇到问题后,我们首先检查一下帧率是否保持在60
帧。 - 定位瓶颈,究竟是
CPU
还是GPU
。我们希望占用率越少越好,一是为了流畅性,二也节省了电力。 - 检查有没有做无必要的
CPU
渲染,例如有些地方我们重写了drawRect:
,而其实是我们不需要也不应该的。我们希望GPU
负责更多的工作。 - 检查有没有过多的离屏渲染,这会耗费
GPU
的资源,像前面已经分析的到的。离屏渲染会导致GPU
需要不断地onScreen
和offscreen
进行上下文切换。我们希望有更少的离屏渲染。 - 检查我们有无过多的
Blending
,GPU
渲染一个不透明的图层更省资源。 - 检查图片的格式是否为常用格式,大小是否正常。如果一个图片格式不被
GPU
所支持,则只能通过CPU
来渲染。一般我们在iOS
开发中都应该用PNG
格式,之前阅读过的一些资料也有指出苹果特意为PNG
格式做了渲染和压缩算法上的优化。 - 检查是否有耗费资源多的
View
或效果,我们需要合理有节制的使用。 - 最后,我们需要检查在我们
View
层级中是否有不正确的地方。例如有时我们不断的添加或移除View
,有时就会在不经意间导致bug
的发生。
测试工具:
Core Animation
,Instruments
里的图形性能问题的测试工具。view debugging
,Xcode 自带的,视图层级。reveal
,视图层级。
参考文章
- 绘制像素到屏幕上
- iOS图形原理与离屏渲染,在1.4.1中,
这也是为什么 CALayer 有一个叫做 opaque 的属性了。如果这个属性为 NO,GPU 将不会做任何合成,而是简单从这个层拷贝,不需要考虑它下方的任何东西(因为都被它遮挡住了)。
中的opaque
属性为NO
,GPU
将不会做任何合成,这句话时错误的,应该是为YES
,GPU
才不会做任何合成。 - iOS 保持界面流畅的技巧
- Advanced Graphics and Animations for iOS Apps(session 419)
- 使用 ASDK 性能调优 - 提升 iOS 界面的渲染性能
- Designing for iOS: Graphics & Performance
- iOS离屏渲染之优化分析
- iOS视图渲染以及性能优化总结
- iOS 离屏渲染
- 深刻理解移动端优化之离屏渲染
- iOS 流畅度性能优化、CPU、GPU、离屏渲染
- iOS 图形性能优化锦集
- 离屏渲染优化详解:实例示范+性能测试
作者:LaiYoung_
链接:https://juejin.im/post/5ace078cf265da23994ee493
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
iOS 性能优化总结的更多相关文章
- 【腾讯Bugly干货分享】微信读书iOS性能优化
本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/578c93ca9644bd524bfcabe8 “8小时内拼工作,8小时外拼成长 ...
- iOS性能优化:Instruments使用实战
iOS性能优化:Instruments使用实战 最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instrument ...
- IOS 性能优化的建议和技巧
IOS 性能优化的建议和技巧 本文来自iOS Tutorial Team 的 Marcelo Fabri,他是Movile的一名 iOS 程序员.这是他的个人网站:http://www.marcelo ...
- iOS性能优化总结
iOS性能优化总结.关于 iOS 性能优化梳理: 基本工具.业务优化.内存优化.卡顿优化.布局优化.电量优化. 安装包瘦身.启动优化.网络优化等. 关于iOS 性能优化梳理: 基本工具.业务优化.内存 ...
- iOS 性能优化收集
iOS 性能调试 instrument Instrument Instrument之Core Animation工具 避免图层混合 ①.确保控件的opaque属性设置为true,确保backgroun ...
- <转>iOS性能优化:Instruments使用实战
最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instruments使用技巧 关于Instruments官方有一个很有 ...
- iOS性能优化之内存管理:Analyze、Leaks、Allocations的使用和案例代码
最近接了个小任务,和公司的iOS小伙伴们分享下instruments的具体使用,于是有了这篇博客...性能优化是一个很大的话题,这里讨论的主要是内存泄露部分. 一. 一些相关概念 很多人应该比较了解这 ...
- 微信读书 iOS 性能优化总结
微信读书作为一款阅读类的新产品,目前还处于快速迭代,不断尝试的过程中,性能问题也在业务的不断累积中逐渐体现出来.最近的 1.3.0 版本发布后,关于性能问题的用户反馈逐渐增多,为此,团队开始做一些针对 ...
- iOS性能优化
最近采用Instruments 来分析整个应用程序的性能.发现很多有意思的点,以及性能优化和一些分析性能消耗的技巧,小结如下. Instruments使用技巧 关于Instruments官方有一个很有 ...
- 关于 iOS 性能优化方面的面试题,
这是我前面几天碰到的面试题: 如何对定位和分析项目中影响性能的地方?以及如何进行性能优化? 我的答案: 定位方法: instruments 在iOS上进行性能分析的时候,首先考虑借助instrum ...
随机推荐
- JDK源码——单例模式
JDK源码中单例模式的应用 1.Runtime类 Runtime类封装了Java运行时的环境.每一个java程序实际上都是启动了一个JVM进程,那么每个JVM进程都是对应这一个Runtime实例,此实 ...
- C#通过Ado.net对连接数据库并进行添加删除等常规操作的代码
如下资料是关于C#通过Ado.net对连接数据库并进行添加删除等常规操作的内容. static string sqlcon = "server=.;database=;Integrated ...
- Problem 2285 迷宫寻宝 (BFS)
题目链接:http://acm.fzu.edu.cn/problem.php?pid=2285 Problem 2285 迷宫寻宝 Accept: 323 Submit: 1247Time Li ...
- C# 反射 Type.GetType()
对于外部调用的动态库应用反射时要用到Assembly.LoadFile(),然后才是获取类型.执行方法等;当用反射创建当前程序集中对象实例或执行某个类下静态方法时只需通过Type.GetType(&q ...
- CAS部署在Windows上
我这里有下载好的cas.war和tomcat7,然后我在将cas.war放在tomcat目录下的webapps下,启动tomcat自动解压war包.浏览器输入http://localhost:8080 ...
- excel VBA动态链接数据库
最近车间制造部有需求,需要通过excel填写的ID获取数据库中的某个取值.研究了一下VBA,简要记录一下代码,仅供参考. Function GETPMAX(SID As String) As Stri ...
- 开发过程中 的一些 补充知识点 + 关于mysql中的日期和时间函数?
参考: https://www.jb51.net/article/23966.htm https://yq.aliyun.com/articles/260389 mysql中的 日期格式是: HHHH ...
- 剑指offer 14:链表中倒数第k个节点
题目描述 输入一个链表,输出该链表中倒数第k个结点. /* public class ListNode { int val; ListNode next = null; ListNode(int va ...
- ubuntu安装nvidia显卡驱动
朋友挖矿,需要给Ubuntu(16.04版本)系统安装nvidia的显卡驱动,请我帮忙.最开始是进行手动安装.无奈的是安装完后进不了图形化界面.今天正好有时间,找了个硬盘装了个Ubuntu进行测试,成 ...
- [原][飞行仿真]helios与dcs world安装,详尽教程
可以参考的博客: http://www.insky.cn/bbs/forum.php?mod=viewthread&tid=58175 http://www.insky.cn/bbs/foru ...