U4 4.0 混合光栅化

简介: 我在蚂蚁 [Codeday#4 广州站的演讲][1]上介绍了为 U4 4.0 新设计的渲染流水线, 我们称为直接合成(Direct Compositing),区别于原生 WebView 同步合成(Synchronous Compositing)的渲染流水线。 新渲染流水线除了更好的系统兼容性和支持独立 GPU 进程外,也希望通过简化合成器架构和支持直接光栅化来提升渲染性能,减少 GPU 内存

我在蚂蚁 Codeday#4 广州站的演讲上介绍了为 U4 4.0 新设计的渲染流水线, 我们称为直接合成(Direct Compositing),区别于原生 WebView 同步合成(Synchronous Compositing)的渲染流水线。

新渲染流水线除了更好的系统兼容性和支持独立 GPU 进程外,也希望通过简化合成器架构和支持直接光栅化来提升渲染性能,减少 GPU 内存占用。降低 GPU 内存占用的意义除了减少应用 OOM 崩溃的概率外,也可以降低出现 GL OOM 虽然没有崩溃但是会出现黑屏/黑块的现象。

渲染流水线中的光栅化这篇文章里,我介绍了各种渲染流水线使用的不同光栅化方式,和它们各自的优劣。在新渲染流水线的实际性能测试中,也基本印证了文中的观点。

直接光栅化对比分块异步光栅化,在动态内容的渲染性能和 GPU 内存占用上会有较为明显的优势,在首屏性能上也有略微的领先,但是对于图层动画特别是惯性滚动动画,也存在一定程度的劣势。在实际的测试中,使用直接光栅化对比使用异步光栅化,测试较为复杂的网页(比如天猫超市),在低端机上的惯性滚动帧率存在一定程度下降。

混合光栅化

为了继续优化低端机的惯性滚动动画性能,我们实验了新的优化方向 —— 混合光栅化。混合光栅化顾名思义就是合成器同时使用直接光栅化和分块异步光栅化,光栅化的决策是以图层为单位,也就是说合成器需要支持不同的图层使用不同的光栅化方式。

通过对部分图层使用异步光栅化的方式,这些图层可以避免在合成输出的线程进行光栅化,合成输出的耗时对比完全直接光栅化会有所减少,同时因为不是所有图层都使用异步光栅化,整体的 GPU 内存占用又优于完全使用异步光栅化的情况。特别是对于存在大量图层的页面,分块缓存占用了较多的 GPU 内存,但实际上大部分图层又比较简单,直接光栅化的开销对比绘制图层的分块来说并不高,对于这种页面,混合光栅化可以在内存占用和性能之间获得一个更好的平衡。

绘制复杂度

为了决定哪些图层使用直接光栅化,哪些图层使用异步光栅化。我们设计了绘制复杂度(Paint Complexity)的概念,所谓绘制复杂度,就是通过对图层 DisplayList 中的绘图指令进行静态分析,计算出一个代表这个 DisplayList 绘制的复杂度值,这个值跟光栅化引擎实际绘制这个 DisplayList 的 CPU 耗时成正比。

通过对 Skia GPU 光栅化绘制耗时的分析,绘制复杂度的算法大致如下:

  1. 绘图指令的基础绘制复杂度为 2,包括 DrawRect,DrawRRect,DrawTextBlob,DrawPath,DrawImage 等
  2. 如果包含图片绘制 +1
  3. 如果包含 Slow Path +1,Slow Path 是指路径包含凹多边形
  4. 如果受 ClipPath 影响,对上述计算出来的指令绘制复杂度进行倍乘,支持嵌套 ClipPath
  5. 如果受 SaveLayer 影响,对上述计算出来的指令绘制复杂度进行倍乘,支持嵌套 SaveLayer

图层光栅化决策

有了图层的绘制复杂度,我们就可以再结合图层的其它属性,比如宽高大小来决定这个图层采用何种光栅化的方式。大致的计算方式如下:

  1. 计算图层的最大可见面积,然后根据最大可见面积和 Viewport 面积的比例,将图层归类到不同层级,比如 <= 1/16,<= 1/8;
  2. 计算图层最大可见面积和总面积的比值,推导出图层的平均绘制复杂度;
  3. 为不同层级的图层设定不同的绘制复杂度阈值,如果图层的平均绘制复杂度大于所属层级的阈值,则选择异步光栅化,反之则选择直接光栅化;

这样的策略就是为了让绘图指令比较复杂,光栅化耗时较长的图层在 Worker 线程去做光栅化,避免阻塞合成输出的线程。

说明:
最大可见面积为图层宽高和 Viewport 宽高的最小值的乘积,代表在当前的 Viewport 下,图层最大可见的区域面积;

Snapdragon Profiler

使用上述策略对天猫超市进行实际计算的结果如上图:

  1. 在首屏有三个图层使用异步光栅化,第一个被顶栏遮盖,第二个是中部的分类选择区域,第三个是“今日疯抢”;
  2. 在中部的商品显示也有三个图层使用异步光栅化,第一个是顶部输入框的右侧,第二个是顶部的分类标签栏,第三个是超大的商品显示图层;
  3. 红色遮罩表示图层的平均绘制复杂度超过所在层级阈值的两倍,光栅化的耗时会比较长;

性能测试与分析

光栅化性能

下面的表格显示了在 Google Pixel 上,天猫超市首屏和中部内容区域合成输出一帧的平均耗时(单位毫秒)。包含三种不同光栅化策略的结果,分别是完全直接光栅化(DIRECT),混合光栅化(HYBIRD),完全分块异步光栅化(ASYNC)。

* Draw (DIRECT) Flush (DIRECT) Total (DIRECT) Draw (HYBIRD) Flush (HYBIRD) Total (HYBIRD) Draw (ASYNC) Flush (ASYNC) Total (ASYNC)
首屏 5.6 4.6 10.2 3.5 3.0 6.5 2.1 2.7 4.8
中部 5.8 4 9.8 2.3 2.7 5 1.2 2.9 4.1

Draw 代表 Display Compositor 绘制一帧 CompositorFrame 的耗时,Skia 执行 2D 绘图指令,生成相应的 GL 指令,Encode 到 Commad Buffer 里面。Flush 代表 GPU Service decode Command Buffer,输出真正的 GL 指令,包括调用 eglSwapBuffers 。Draw 和 Flush 两者分别在不同的线程,可以异步运行,在运行中可能存在部分交叠,所以实际一帧的合成耗时并不等于两者简单的叠加,Total 栏位显示两者之和只是为了方便理解。

从数据我们可以看出,天猫超市页面的绘制复杂度的确很高,如果完全使用直接光栅化,在设备性能不足的情况下,因为一帧的合成输出(包括光栅化)耗时较长,惯性滚动下比较容易出现掉帧。而使用混合光栅化时,合成输出的耗时比较接近分块异步光栅化,对低端机更为友好。

GPU 内存占用

* DIRECT HYBIRD ASYNC
首屏静止状态 110 130 155
快速上下滑动后,中部静止状态 120 140 155
快速上下滑动过程峰值 120 185 190

说明:
内核运行在单进程模式下,使用 meminfo 查看进程的内存占用,GPU 内存占用可以认为是 Gfx dev 和 GL mtrack 两项之和,因为测试 UI 非常简单,Android UI 本身的 GPU 占用很低(不超过 2m),所以基本可以认为进程的 GPU 内存占用都是内核分配的

分块缓存池的大小跟 WebView 的大小相关,Pixel 的屏幕分辨率是 1920 x 1080,WebView 运行在全屏模式下,大小为 1920 x 1080,根据这个 WebView 大小,我们设定分块缓存池的 Soft Limit 为 60m,如果分块缓存累积超过 Soft Limit,则会马上释放不可见的分块,另外也会周期主动释放超过一段时间不使用的分块。因为每个 WebView 的合成器有自己独立的分块缓存池,如果存在多个可见 WebView,总的分块缓存内存会是所有 WebView 的累加。

在首屏静止状态,我们可以看到直接光栅化的 GPU 占用大概在 110m,从 GPU Profiler 工具可以看到这些内存基本上是由两部分组成:

  1. 图片解码后上传纹理生成的纹理缓存;
  2. Skia 内部生成的缓存,Skia 生成的缓存主要是 Atlas 缓存,用于拼接字形和小图标;

混合光栅化在上述的内存分配外,再加上 20m 左右的分块纹理缓存,而异步光栅化则大概分配了 45m 左右的分块纹理缓存。

上下滑动后,在中部静止状态下的内存占用和首屏差不多,混合光栅化仍然保留了 20m 左右的分块纹理缓存,而异步光栅化需要的分块纹理缓存较首屏要少一些,大概 35m 左右。

从上下滑动过程的峰值我们可以看出,混合光栅化的 GPU 内存占用基本跟异步光栅化持平,因为两者的分块纹理缓存分配都达到或者略微超过了分块缓存池的 Soft Limit,主要的原因是天猫超市中部的超大内容图层在混合光栅化下使用了异步光栅化,所以它在滚动时同样需要不断分配新的分块缓存。不过实际的观察发现,混合光栅化达到峰值的频率和停留在峰值的时间会比异步光栅化要少。

从上述的分析我们可以看到混合光栅化对比完全分块异步光栅化在 GPU内存占用上的一些优势:

  1. 静止状态一般减少 15 ~ 25m 的 GPU 内存占用,这个值取决于页面的图层结构和图层的绘制复杂度,另外 WebView 越大(屏幕分辨率超过 1080p),可见 WebView 越多,这个值就越大(倍乘增长);
  2. 滚动过程的峰值,虽然在部分情况下,混合光栅化跟异步光栅化差别不大,不过达到峰值的频率和停留在峰值的时间会比异步光栅化要少;

当然,如果采用完全的直接光栅化,GPU 内存优化的结果是非常明显的,特别是在页面惯性滚动过程的峰值状态上。

进阶优化

不可滚动页面

有很多页面是不可滚动的,它们通常用于展现互动内容。混合光栅化主要是为了提升惯性滚动的性能,所以对于不可滚动的页面我们可以设定不使用混合光栅化,全部使用直接光栅化。对于不可滚动的页面,使用直接光栅化有助于节省 GPU 内存供 WebGL,Canvas 这样的 GPU 内存占用大户使用,另外对动态变化的内容,直接光栅化对性能提升也有一定帮助。

图层树简单的页面

如果页面的图层树比较简单,也就是说图层数量比较少并且绝大部分图层的绘制复杂度都比较低。这意味着即使某个图层可能绘制耗时较长,但是总耗时还是比较低的。所以,我们可以采用类似下面针对高性能设备的优化策略来进一步减少 GPU 内存的占用。测试验证对于百度搜索首页和部分新闻/电商二级内容页面的 GPU 内存占用会有比较明显的优化效果,接近完全直接光栅化的水平。

高性能设备

对于高性能的移动设备来说,绘制同样复杂度的图层,绘制的耗时更低。所以我们可以采用以下的一些策略来减少使用异步光栅化,进一步降低 GPU 内存占用,使得绝大部分情况下 GPU 内存占用都接近或者和实现与完全直接光栅化基本一致的结果。

  1. 提高每个层级的绘制复杂度阈值,比如直接乘以两倍;
  2. 只对面积较小的图层使用异步光栅化,超过一定面积的图层强制使用直接光栅化,比如限制最大可见面积和 Viewport 面积的比例小于 1/2 的图层才允许选择异步光栅化;
相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
目录
相关文章
|
Web App开发 缓存 移动开发
V8 JS AOT化的探索与实践
JS 语言的动态性非常优秀,其弱类型等语言特性也使得一线业务开发者更容易上手,但这也导致 JS 每一次运行前都要重复编译,使得 JS 的执行性能不理想;虽然之前 UC 内核有做过 Code Cache 方案,但支持的场景不够完整,与原生 Native 的技术方案比,尤其是首次启动场景(如各类大促活动等)还是有比较大的差距。为了能尽可能做到与 Native 对标,缩小性能差距,同时让业务开发者无感,我们开发了 JS AOT 功能。本分享将结合目前集团内自有业务形态,以及 JS 在 Web 中的执行过程,介绍JS AOT是如何设计和实现的,以及能给业务带来哪些收益。本篇分享来自阿里巴巴的喻世江在第
2826 0
V8 JS AOT化的探索与实践
|
Python
解决GNURadio自定义Python OOT块-导入块时报错问题
解决GNURadio自定义Python OOT块-导入块时报错问题
518 0
|
Java p3c 开发者
阿里java开发规范学习(附P3C IDEA插件 帮助规范的养成)
浅析 阿里巴巴 Java 开发规约 (未完成) contents 为什么要学 编程规约 P3C IDEA 插件 why-use 我们知道,一般稍微大一点的公司,都会在系统架构设计完成之后,编码工作开始之前,给出一份属于自家公司,或是自家团队给出的编码规范文...
5627 0
|
9月前
|
人工智能 并行计算 异构计算
MT-TransformerEngine:国产训练核弹!FP8+算子融合黑科技,Transformer训练速度飙升300%
MT-TransformerEngine 是摩尔线程开源的高效训练与推理优化框架,专为 Transformer 模型设计,通过算子融合、并行加速等技术显著提升训练效率,支持 FP8 混合精度训练,适用于 BERT、GPT 等大型模型。
524 10
MT-TransformerEngine:国产训练核弹!FP8+算子融合黑科技,Transformer训练速度飙升300%
|
存储 NoSQL 算法
聊一聊分布式锁的设计模型
什么是分布式锁?对于这个问题,相信很多同学是即熟悉又陌生。随着分布式系统的快速发展与广泛应用,针对共享资源的互斥访问也成为了很多业务必须要面对的需求,这个场景下人们通常会引入分布式锁来解决问题。我们通常会使用怎么样的分布锁服务呢?在使用分布式锁过程中,总还是会提出这样、那样的新需求,看起来找不到一个分布式锁场景的大一统的解决方案。那么,分布式锁内部究竟是怎么实现的?或者说应该怎么实现呢?这个是我们这篇文章希望探讨的。
1959 1
聊一聊分布式锁的设计模型
|
存储 Shell 开发工具
GitHub git push超过100MB大文件失败(write error: Broken pipe)完美解决
GitHub git push超过100MB大文件失败(write error: Broken pipe)完美解决
2057 0
GitHub git push超过100MB大文件失败(write error: Broken pipe)完美解决
|
资源调度 前端开发 JavaScript
web实现酷炫的canvas粒子动画背景
web实现酷炫的canvas粒子动画背景
629 0
|
机器学习/深度学习 PyTorch 算法框架/工具
使用FP8加速PyTorch训练的两种方法总结
在PyTorch中,FP8数据类型用于高效训练和推理,旨在减少内存占用和加快计算速度。虽然官方尚未全面支持,但在2.2版本中引入了`torch.float8_e4m3fn`和`torch.float8_e5m2`。文章通过示例展示了如何利用FP8优化Vision Transformer模型,使用Transformer Engine库提升性能,并探讨了PyTorch原生FP8支持的初步使用方法。实验表明,结合TE和FP8,训练速度可提升3倍,性能有显著增强,特别是在NVIDIA GPU上。然而,PyTorch的FP8支持仍处于试验阶段,可能带来不稳定性。
742 0
|
缓存 前端开发 安全
前后端分离架构下Java Web开发的挑战与解决方案
前后端分离架构下Java Web开发的挑战与解决方案
355 1
|
安全 数据建模 网络安全
SSL证书三种类型区别
SSL证书三种类型
704 0