性能优化学习

  • 离屏渲染问题
  • Instrument之Core Animation调优

离屏渲染问题

  • GPU 渲染机制:

CPU 计算好显示内容提交到GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按 VSync 信号逐行进行读取帧缓冲区数据,经过可能的数模转换传递给显示器显示。

  • GPU 渲染的两种方式:

    • On-Screen Rendering
      当前屏幕渲染,指的是GPU的渲染操作是在当前用于显示的屏幕缓冲区中进行。
    • Off-Screen Rendering
      离屏渲染,指的是GPU在当前屏幕缓冲区以外新开辟一个缓冲区进行渲染操作。
  • 离屏渲染的性能问题主要体现在两个方面:

    • 创建缓冲区
      想要进行离屏渲染,首先要创建一个新的缓冲区。
    • 上下文切换
      离屏渲染的过程需要多次切换上下文环境:
      • 首先是从当前屏幕切换到离屏;
      • 等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上又需要将上下文环境从离屏切换到当前屏幕。上下文切换是要付出很大的代价的。
  • 特殊的离屏渲染

    • 如果将不在GPU的当前屏幕缓冲区中进行的渲染都称为离屏渲染,那么还有一种特殊的“离屏渲染”方式:CPU渲染。
    • 如果重新了drawRect 方法,并且使用任何Core Graphics 的技术进行了绘制操作,就涉及到了CPU渲染。整个渲染过程由CPU在App内同步地完成,渲染得到的bitmap最后再交给GPU用于显示。
    • Core Graphics 通常是线程安全的,可以进行异步绘制,显示的时候在放回主线程,简单的异步绘制的流程如下:
- (void)display {
    dispatch_async(backgroundQueue, ^{
        CGContextRef ctx = CGBitmapContextCreate(...);
        // draw in context...
        CGImageRef img = CGBitmapContextCreateImage(ctx);
        CFRelease(ctx);
        dispatch_async(mainQueue, ^{
            layer.contents = img;
        });
    });
}
  • 离屏渲染的触发方式:
    • shouldRasterize(光栅化)
    • masks(遮罩)
    • shadows(阴影)
    • edge antialiasing(抗锯齿)
    • group opacity(不透明)
    • 复杂形状设置圆角等
    • 渐变

Instrument之Core Animation调优

  • Color Blended Layers 参考链接

    • 图层颜色混合
      • 屏幕上每一个点都是一个像素,像素有R、G、B三种颜色构成(可能还有alpha值)。如果某一块区域上覆盖了多个layer,最后的显示效果受到这些layer的共同影响。举个例子,上层是蓝色(RGB=0,0,1),透明度为50%,下层是红色(RGB=1,0,0)。那么最终的显示效果是紫色(RGB=0.5,0,0.5)。这种颜色的混合(blending)需要消耗一定的GPU资源,颜色混合越多,那么GPU通过混合纹理计算出像素的RGB值需要消耗的时间就越长,GPU的使用率就越高,如果只想显示最上层的蓝色,可以把它的透明度设置为100%(默认opaque为YES),且设置背景色(中文需要特殊处理label.layer.masksToBounds = YES),这样GPU会忽略下面所有的layer,从而节约了很多不必要的运算
    • 调试
      • Color Blended Layers 主要用于检测哪里发生了图层混合,并用红色标记出来。因此我们需要尽可能减少看到的红色区域,一旦发现应该想法消除。
      • 重要是的是backgroundColor属性,如果不设置这个属性,控件依然被认为是透明的,所以第一个优化就是设置控件的backgroundColor 属性。
      • 如果label文字有中文,依然会出现图层混合,这个是因为此时多了一个sublayer,需要多设置一个Label.layer.masksToBounds = YES;
      • 对于 UIImageView 来说,不仅他自身需要是不透明,它的图片也不能包含有alpha通道,如果图片含有alpha通道这样最好是找UI修改图片。
  • Color Hits Green and Misses Red 参考链接

    • 光栅化
      • 光栅化是将一个layer 预先渲染成位图(bitmap),然后加入缓存中。如果对于阴影效果这样较消耗资源的静态内容进行缓存,可以得到一定幅度的性能提升(label.layer.shouldRasterize = YES)。
    • 调试
      • Instrument中开启Color Hits Green and Misses Red,如果命中缓存则显示绿色,否则显示为红色,显然绿色越多越好,红色越少越好。
    • 光栅化的核心思想是缓存。通过滑动可以看到:

      • 上下微小幅度滑动时,一直是绿色
      • 上下较大幅度滑动,新出现的label一开始是红色,随后变成绿色
      • 如果静止一秒钟,刚开始滑动时会变红 * 因为layer进行光栅化后渲染成位图放在缓存中。当屏幕出现滑动时,直接从缓存中读取而不必渲染,所以会看到绿色。当新的label出现时,缓存中没有个这个label的位图,所以会变成红色。第三点比较关键,缓存中的对象有效期只有100ms,即如果在0.1s内没有被使用就会自动从缓存中清理出去。这就是为什么停留一会儿再滑动就会看到红色。

      • 光栅化的缓存机制是一把双刃剑,先写入缓存再读取有可能消耗较多的时间。因此光栅化仅适用于较复杂的、静态的效果。通过Instrument的调试发现,这里使用光栅化经常出现未命中缓存的情况,如果没有特殊需要则可以关闭光栅化,而且开启光栅化还会导致离屏渲染。

  • Color Copied Images

    • 颜色格式

      • 像素在内存中的布局和它在磁盘中的存储方式并不相同。考虑一种简单的情况:每个像素有R、G、B和alpha 四个值,每个值占用1个字节,因此每个像素占用4字节的内存空间。一张1920*1080的照片一共2,073,600个像素,因此占用了超过8Mb的内存。但是一张同样分辨率的PNG格式或JPEG格式的图片一般情况下不会有这么大。这是因为JPEG将像素数据进行了一种非常复杂且可逆的转化。
      • 当打开JPEG格式的图片时,CPU会进行一系列运算,将JPEG图片解压成像素数据。显然这个工作会消耗不少时间,所以不应该在滑动时进行,我们应该预先处理好图片。借用WWDC上PPT来说明:性能优化学习_第1张图片
        Commit Transaction和Decode在同一帧内进行,如果这两个操作的耗时超过16.67ms,Draw Calls就会延迟到下一帧,从而导致fps值的降低。下面是Commit Transaction的详细流程:性能优化学习_第2张图片

      • 在第三步的Prepare中 CPU 主要处理两件事:

        • 1、把图片从PNG或者JPEG等格式中解压出来,得到像素数据
        • 2、如果GPU不支持这种颜色格式,CPU需要进行格式转换

      比如应用中下载的网络图片,而GPU恰好不支持这个格式,这就需要CPU预先进行格式转化。Color Copied Images 就用来检测这种实时的格式转化,如果有则会将图片标记为蓝色,需要做出处理。

  • Color Misaligned Images

    • 图片大小
      • 在项目中,从网络请求的图片大小不一,但是展示的UIimageView有时候确是固定的。这时候就需要对图片进行缩放了,图片的缩放需要占用时间,因此尽可能保证无论是本地图片还是网络获取的图片大小,都要与frame设置的大小保持一致。
    • 调试
      *Color Misaligned Images 表示如果图片需要缩放则标记为黄色,如果没有像素对齐则标记为紫色。
  • Color Offscreen-Rendered Yellow

    • 正常的渲染通道 (Render-Pass)性能优化学习_第3张图片
      首选,OpenGL 提交一个命令到Command Buffer ,随后GPU 开始渲染,渲染结果放到 Render Buffer 中,这是正常的流程。单丝有一些复杂的效果是无法直接渲染出来的,需要分布渲染最后再组合起来,比如添加一个蒙版(mask):性能优化学习_第4张图片在前两个通道中,GPU 分别得到了纹理(texture,也就是那个相机图标)和 layer(蓝色的蒙版)的渲染结果。但是这两个渲染结果没有直接放入Render Buffer 中,也就表示这是离屏渲染。直到第三个渲染通道,才把两者组合起来放入 Render Buffer 中。离屏渲染意味着把渲染结果临时保存,等到用到的时候再取出,因此相当于普通的渲染更占用资源。
    • Color Offscreen-Rendered Yellow 会把需要离屏渲染的地方标记为黄色,大部分情况下我们要尽可能避免黄色的出现。离屏渲染可能会自动触发,也可以手动触发。
    • 重写drawRect方法
    • 有mask或者是阴影(layer.masksToBounds, layer.shadow*),模糊效果也是一种mask
    • layer.shouldRasterize = true
      前两个会自动触发离屏渲染,第三种方法是手动开启离屏渲染。
    • 设置cornerRadius本身并不会导致离屏渲染,但很多时候它还需要配合layer.masksToBounds = true使用。根据之前的总结,设置masksToBounds会导致离屏渲染。解决方案是尽可能在滑动时避免设置圆角,如果必须设置圆角,可以使用光栅化技术将圆角缓存起来:
    // 设置圆角
    label.layer.masksToBounds = true  
    label.layer.cornerRadius = 8  
    label.layer.shouldRasterize = true  
    label.layer.rasterizationScale = layer.contentsScale  
  • Color Non-Standard Surface Formats (不标准的表面颜色格式)

  • Color Immediately

    • 颜色刷新频率
    • 当执行颜色刷新的时候移除10ms的延迟,因为可能在特定情况下你不需要这些延迟,所以使用此选项加快颜色刷新的频率。一般这个调试选项我们是用不到的。
  • Color Compositing Fast-Path Blue

    • 离屏渲染的最后一步是把此前的多个路径组合起来。如果这个组合过程能由CPU完成,就会大量减少GPU的工作。这种技术在绘制地图中可能用到。

    • Color Compositing Fast-Path Blue用于标记由硬件绘制的路径,蓝色越多越好。

  • Flash Updated Regions

    • 刷新视图时,我们应该把需要重绘的区域尽可能缩小。对于未发生变化的内容则不应该重绘, * Flash updated Regions用于标记发生重绘的区域。
    • 一个典型的例子是系统的时钟应用,绝大多数时候只有显示秒针的区域需要重绘
  • 总结: 优化滑动性能主要涉及到三个方面

    避免图层混合
        确保控件的opaque属性设置为true,确保backgroundColor和父视图颜色一致且不透明
        如无特殊需要,不要设置低于1的alpha值
        确保UIImage没有alpha通道
    避免临时转换
        确保图片大小和frame一致,不要在滑动时缩放图片
        确保图片颜色格式被GPU支持,避免劳烦CPU转换
    慎用离屏渲染
        绝大多数时候离屏渲染会影响性能
        重写drawRect方法,设置圆角、阴影、模糊效果,光栅化都会导致离屏渲染
        设置阴影效果是加上阴影路径
        滑动时若需要圆角效果,开启光栅化

参考文章:https://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
https://developer.apple.com/videos/play/wwdc2014/419/
https://bestswifter.com/uikitxing-neng-diao-you-shi-zhan-jiang-jie/#

你可能感兴趣的:(iOS)