书架卡顿问题引发的显示类知识梳理&性能检测

来源:互联网 发布:手机加油软件代理 编辑:程序博客网 时间:2024/05/16 10:07

1. 问题描述

百度阅读 iOS 版书架在宫格、列表模式下进行快速滑动均感觉到明显卡顿,当书架中全部为图书\小说时滑动较流畅,但生成文件夹后卡顿加强。

2. iOS 显示原理

  • Vsync是什么?
  • CPU\GPU协同方式?
  • V-Sync 机制是什么?
  • 双缓冲工作原理?
  • 垂直同步工作原理?
  • 掉帧卡顿是如何产生的?

避免复制粘贴,上面的这些问题可以从下面这篇文章找到答案:

ibireme:如何让iOS 保持界面流畅?这些技巧你知道吗

3. 检测手段

3.1 用YYFPSLabel进行简单的监测

@ibreme 的 YYKit中一个简易组件,可加入到要被检测的 VC 的- (void)viewDidLoad 中。

    // 加测试 fps    _fpsLabel = [[YYFPSLabel alloc] initWithFrame:CGRectMake(0, 100, 200, 200)];    [_fpsLabel sizeToFit];    // 当前顶层窗口    UIWindow *window = [[UIApplication sharedApplication].windows lastObject];    // 添加到窗口    [window addSubview:_fpsLabel];

通过这个控件检测发现,书架卡顿时,CPU 消耗很少,最卡的时候帧数也在55-60之间。排除 CPU 的原因,要监测 GPU 我们开始使用更锋利的匕首——instruments。

3.2 用 instruments全面检测

  • GPU Driver
  • Core Animation
  • OpenGL ES Driver
  • 模拟器中的『Color debug options View debugging』

3.2.1 观测的指标

  • GPU Driver 相关的检查选项:

    • 设备利用率 Device Utilization %:GPU在渲染上花费的所有时间。>95%意味着该应用程序是GPU绑定的。
    • 渲染利用率 Renderer Utilization %:GPU在绘制像素上花费的时间。超过了~50%,就意味着你的动画可能对帧率有所限制,很可能因为离屏渲染或者是重绘导致的过度混合。

    • Tiler利用率 Tiler Utilization %:GPU在处理顶点上花费的时间。超过了~50%,就意味着你的动画可能限制于几何结构方面,也就是在屏幕上有太多的图层占用了。

    • 分割次数 Split count:帧分割的数量,顶点信息无法使用分配的缓冲区。

  • Core Animation相关的检查选项:

    • Color Blended layers
      勾选这个选项后,blended layer 就会被显示为红色,而不透明的layer则是绿色。我们希望越少红色区域越好。
    • Color Hits Green and Misses Red
      如果shouldRasterize被设置成YES,对应的渲染结果会被缓存,如果图层是绿色,就表示这些缓存被复用;如果是红色就表示缓存会被重复创建,这就表示该处存在性能问题了。如果光栅化的层变红得太频繁那么光栅化对优化可能没有多少用处。位图缓存从内存中删除又重新创建得太过频繁,红色表明缓存重建得太迟。可以针对性的选择某个较小而较深的层结构进行光栅化,来尝试减少渲染时间。

    • Color copied images
      这个选项主要检查我们有无使用不正确图片格式,若是GPU不支持的色彩格式的图片则会标记为青色,则只能由CPU来进行处理。我们不希望在滚动视图的时候,CPU实时来进行处理,因为有可能会阻塞主线程。

    • Color misaligned images
      这个选项检查了图片是否被放缩,像素是否对齐。被放缩的图片会被标记为黄色,像素不对齐则会标注为紫色。

    • Color Offscreen-Rendered Yellow
      开启后会把那些需要离屏渲染的图层高亮成黄色,这就意味着黄色图层可能存在性能问题。

WWDC心得与延伸:iOS图形性能

3.2.2 使用方法

滑动书架,查看卡顿时 GPU Driver 检测到的上述几个指标。发现Renderer Utilization和Tiler Utilization都达到了90-100之间。可以确认是GPU 处理顶点和渲染花费很大性能。

3.2.3 相关概念

1)光栅化(Rasterize)
将图转化为一个个栅格组成的图像,每个元素对应帧缓冲区中的一像素。

如何理解OpenGL中着色器,渲染管线,光栅化等概念?

2)离屏渲染(Off-Screen Rendering)

(What)什么是离屏渲染?
GPU在当前屏幕缓冲区之外开辟一个新的缓冲区进行渲染操作,并多次切换上下文。因此比当前屏幕渲染更耗费性能。

(When)什么时候会离屏渲染?
下面的这些属性会触发离屏绘制

  • shouldRasterize(光栅化)

    shouldRasterize = YES在其他属性触发离屏渲染的同时,会将光栅化后的内容 缓存起来,如果对应的layer及其sublayers 没有发生改变 ,在下一帧的时候可以直接复用。

    光栅化是把GPU的操作转到CPU上了,生成位图缓存,直接读取复用。

  • masks(遮罩)

  • shadows(阴影)
  • edge antialiasing(抗锯齿)
  • group opacity(不透明)
  • 复杂形状设置圆角等
  • 渐变

还有一种特殊的离屏渲染——CPU渲染:如果我们重写了drawRect方法,并且使用任何Core Graphics的技术进行了绘制操作,就涉及到了CPU渲染。整个渲染过程由CPU在App内 同步地
完成,渲染得到的bitmap最后再交由GPU用于显示。

(Why)为什么需要离屏渲染?
圆角、阴影、遮罩等效果需要预合成,在合成之前不能直接在当前屏幕绘制,因此需要离屏渲染完成之后,切换上下文到当前屏幕绘制。

离屏渲染学习笔记

iOS 离屏渲染的研究

3) 混合(Blending)
What
blending指混合像素颜色的计算。若两个图层重叠,第一个图层有透明度,则最终像素颜色计算需要考虑第二个图层。

When
alpha < 1

Why 导致性能耗损
如果是不透明的图层,则该像素颜色显示即为图层在该像素的颜色。若含透明度,则需要引入更多计算,计算下面的图层混合后在该像素显示的颜色。

4. 避免卡顿的解决办法

4.1 开发时的tips

1、每次都看一下有没有能重用的 cell,而不是永远重新新建(这个是 UITableView 的常识)
2、Cell 里尽量不要用 UIView 而是全部自己用 drawRect 画(之前因为 iOS 有 bug,这样做会有性能上质的飞越。也有很多大神写过很多文章解释原理,有兴趣的自己去看看吧我就不做复制粘贴了。后来 iOS 也改掉了这个问题,这么做的效果就没那么明显了。)
3、图片载入放到后台进程去进行,滚出可视范围的载入进程要 cancel 掉
4、圆角、阴影之类的全部 bitmap 化,或者放到后台 draw 好了再拿来用
5、Cell 里要用的数据提前缓存好,不要现用现去读文件
6、数据量太大来不及一次读完的做一个 load more cell 出来,尽量避免边滚边读数据,这样就算是双核的 CPU 也难保不会抽

4.2 避免离屏渲染

1)阴影绘制:使用ShadowPath来替代shadowOffset等属性的设置。
2)裁剪图片为圆。
3)blending
4)不要在滚动视图使用 cornerRadius\mask\ shouldRasterize;

如果一定要实现圆角效果:

  • 视图内容不变的情况下、又图省事的话,可以:
  // 开启光栅化,使视图渲染内容被缓存起来,下次绘制的时候可以直接显示缓存  self.layer.shouldRasterize = YES;    self.layer.rasterizationScale = [UIScreen mainScreen].scale;
  • 视图内容变化时:
    后台预先生成圆角图片,并缓存起来,再在主线程显示,避免离屏渲染。
  • 视图背景为单色时,可在图片上面覆盖一个镂空圆形图片。

滚动类视图(tableview\collection view)的重绘是很频繁的(因为Cell的复用),如果Cell的内容不断变化,则Cell需要不断重绘,如果此时设置了cell.layer可光栅化。则会造成大量的离屏渲染,降低图形性能。

WWDC心得与延伸:iOS图形性能

极客学院:性能调优

推酷:Core Animation系列之CADisplayLink

4.3 分析界面性能问题的步骤

1)定位帧率,为了给用户流畅的感受,我们需要保持帧率在60帧左右。当遇到问题后,我们首先检查一下帧率是否保持在60帧。

2)定位瓶颈,究竟是CPU还是GPU。我们希望占用率越少越好,一是为了流畅性,二也节省了电力。

3)检查有没有做无必要的CPU渲染,例如有些地方我们重写了drawRect,而其实是我们不需要也不应该的。我们希望GPU负责更多的工作。

4)检查有没有过多的offscreen渲染,这会耗费GPU的资源,像前面已经分析的到的。offscreen 渲染会导致GPU需要不断地onScreen和offscreen进行上下文切换。我们希望有更少的offscreen渲染。

5)检查我们有无过多的Blending,GPU渲染一个不透明的图层更省资源。

6)检查图片的格式是否为常用格式,大小是否正常。如果一个图片格式不被GPU所支持,则只能通过CPU来渲染。一般我们在iOS开发中都应该用PNG格式,之前阅读过的一些资料也有指出苹果特意为PNG格式做了渲染和压缩算法上的优化。

7)检查是否有耗费资源多的View或效果。我们需要合理有节制的使用。例如,UIBlurEffect。

8)最后,我们需要检查在我们View层级中是否有不正确的地方。例如有时我们不断的添加或移除View,有时就会在不经意间导致bug的发生。像我之前就遇到过不断添加View的一个低级错误。我们希望在View层级中只包含了我们想要的东西。

5. 书架卡顿问题定位

5.1 问题定位的思路

  • 根据问题复现缩小范围
    滑动到全部为图书时相对流畅
    滑动到有文件夹时出现严重卡顿
    宫格\列表都卡顿
  • 由于这部分代码不是自己写的,不是很了解逻辑,所以先用简易控件 YYFPSLabel看一下 CPU 消耗,发现帧数在出现明显卡顿时仍保持在55-60之间,可排除 CPU。
  • 用 Instruments 中的 GPU Driver 检测 GPU 的相关指标,判断可能与顶点和渲染有关。
  • code review 文件夹功能的 view,发现书架中使用了大量 layer.cornerRadius。
  • 注释掉初步判断可能为问题的代码,继续用 GPU Driver 检测,发现指标上升,滑动流畅。定位改善圆角可起到优化性能的作用。

5.2 解决方法:

1)到处是圆角。直接覆盖一张中间为圆形透明的图片,或与 PM 沟通,把不是必须为圆角的改为直角;
2) 书架 blended layer 太多,用Color Blended layers检测几乎满屏红色,可优化;
3)翻转到书城,再返回书架后,用Color off-screen rendering 满屏黄色,可优化;

小心别让圆角成了你列表的帧数杀手

当滚动类视图中需要使用大量圆角时,要注意卡顿。

6. 总结画圆角的几种方式:

1)layer.cornerRadius

aImageView.layer.cornerRadius = aImageView.frame.size.width/2.0;  aImageView.layer.masksToBounds = YES;

优点:使用简单

缺点:不适合大量圆角的场景。若有大量圆角,用此方式会造成离屏渲染增加 GPU 消耗。

测评:ios9.0之前UIImageView和UIButton都高亮为黄色。ios9.0之后只有UIButton高亮为黄色。

2) 在 drawRect:中绘制

- (void)drawRect:(CGRect)rect {   CGRect bounds = self.bounds;   [[UIColor whiteColor] set];   UIRectFill(bounds);   [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:8.0] addClip];   [self.image drawInRect:bounds];}

优点:把 GPU的压力转给 CPU,适用于 CPU 压力不大的情况。可以写在SDWebImage的completed回调里,在主线程异步绘制。 也可以封装到UIImageView里,写了个DSRoundImageView。后台线程异步绘制,不会阻塞主线程。

缺点:要重写视图,有点麻烦,CPU\内存消耗增大。

测评:没离屏渲染(但是CPU消耗和内存占用会很大)

3)layer.mask

CAShapeLayer *layer = [CAShapeLayer layer];  UIBezierPath *aPath = [UIBezierPath bezierPathWithOvalInRect:aImageView.bounds];  layer.path = aPath.CGPath;  aImageView.layer.mask = layer;

优点:可不局限于圆角,由 mask 控制边角显示为什么样。

缺点:效率和 layer.cornerRadius 一样。

4)直接覆盖一张中间为圆形透明的图片
这种方法就是多加了一张透明的图片,GPU计算多层的混合渲染blending也是会消耗 一点性能的,但比第一种方法还是好上很多的。

优点:无离屏渲染

缺点:得求 UI 给个图,如果是非纯色背景,此方法不适合。

测评:无任何高亮,说明没离屏渲染。

如果要效率(例如要提高table view的滚动帧数),就多用方法二。要方便,自然是方法一。如果需要的特殊形状UIBezierPath对象无法构成,则考虑方法三。

5)SDWebImage处理图片时Core Graphics绘制圆角

//UIImage绘制为圆角  int w = imageSize.width;  int h = imageSize.height;  int radius = imageSize.width/2;    UIImage *img = image;  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();  CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);  CGRect rect = CGRectMake(0, 0, w, h);  CGContextBeginPath(context);  addRoundedRectToPath(context, rect, radius, radius);  CGContextClosePath(context);  CGContextClip(context);  CGContextDrawImage(context, CGRectMake(0, 0, w, h), img.CGImage);  CGImageRef imageMasked = CGBitmapContextCreateImage(context);  img = [UIImage imageWithCGImage:imageMasked];  CGContextRelease(context);  CGColorSpaceRelease(colorSpace);  CGImageRelease(imageMasked);

以上代码可以写成UIImage的类别:UIImage+RoundImage.h 并在SDWebImage库里处理image的时候使用类别方法绘制圆角并缓存。

测评:无任何高亮,说明没离屏渲染,而且内存占用也不大。(看起来是最优解)

7. iOS 9的优化

iOS 9.0 之前,UIimageView、UIButton设置圆角都会触发离屏渲染

iOS 9.0 之后,UIButton设置圆角会触发离屏渲染,而UIImageView里png图片设置圆角不会触发离屏渲染了,如果设置其他阴影效果之类的还是会触发离屏渲染的。

8. 备注(instruments的 bug)

记录一个instruments 的问题,避免其他人踩坑。

apple 说自己的 xcode instruments有个 bug ,用 instruments 进行调试时在 xcode 7上经常出现无法选择测试设备的情况,让人头疼不已。 但苹果把它从 xcode 6.4开始就作为 known issue,现在都 xcode7.2了,不知何时才能修好。

看开发者网站上,有位童鞋的法子挺好,简述就是:

关手机-拔设备-关 xcode\instruments-开手机-插设备-clean-build-profile-好使了。

0 0
原创粉丝点击