当前位置: 首页 > 科技观察

看这篇文章了解直播卡顿优化的那些事儿

时间:2023-03-17 16:59:16 科技观察

希望这篇文章能让大家对卡顿问题有一个比较全面的认识,认识卡顿是什么,卡顿的原因,卡顿的分类,卡顿的优化freeze有了一些积累的经验,我们就可以有针对性的解决App流畅度的问题。接下来将从以下五个方面进行介绍:什么是卡顿,为什么会出现卡顿,如何评价卡顿,如何优化卡顿1、什么是卡顿,顾名思义,就是界面不流畅。我们知道,手机的屏幕图像是按照一定的频率刷新的。理论上,24帧的图像更新可以让人感觉连贯。但实际上,这只是针对普通视频而言。对于一些交互性强或者敏感的场景,比如游戏,至少需要60帧,30帧的游戏会让人感觉不舒服;位移或30帧的大型动画会造成明显的挫败感;手部动画如果能达到90Frames甚至120Frames就会让人觉得很细腻,这也是为什么最近厂商都把重点放在了高刷新卡上。对于用户来说,从体感上来说,卡顿大致可以分为以下几类:这些体验对于用户来说可以说是非常不好的,甚至会造成感官上的烦躁,进而让用户不愿意在我们的App里停留。可以说流畅的体验对于用户来说是非常重要的。2、为什么会出现卡顿用户体感卡顿问题的原因有很多,而且往往是一个复杂的问题。为了重点起见,这里我们只考虑真正意义上的掉帧卡顿。2.1不可避免的VSYNC我们通常说的屏幕刷新率是60帧,所有的操作都需要在16ms内完成,以免卡顿。但是这里有几个基本问??题需要弄清楚:为什么是16ms?什么需要在16ms内完成?系统如何尽量保证任务在16ms内完成?如果在16ms内没有完成,就一定会卡顿吗?这里先回答第一个问题:为什么是16ms。早期的Android没有vsync机制,CPU和GPU的配合也比较混乱,这也造成了著名的撕裂问题,即CPU/GPU直接更新正在显示的屏幕缓冲区,导致画面撕裂。后来Android引入了双缓冲机制,但是buffer的切换也需要一个更合适的时机,即屏幕扫描完上一帧之后的时机,这就是引入vsync的原因。早先一般的屏幕刷新率是60fps,所以每次vsync信号的间隔也是16ms。不过随着技术的更迭和厂商对流畅度的追求,90fps、120fps的手机越来越多,对应的间隔也变成了11ms、8ms。既然有了VSYNC,谁在消费VSYNC?其实Android中有两个VSYNC消费者,分别对应两种VSYNC信号,分别是VSYNC-app和VSYNC-sf,分别对应上层视图绘制和surfaceFlinger的合成,我们接下来会详细讨论。这里还有一些比较有意思的点。一些制造商有垂直同步偏移设计。App和sf的vsync信号之间存在偏移,这也在一定程度上使得App和sf的协同效果更好。2.2View的流浪生活在说下一部分之前,先介绍一个话题:一个View是如何显示在屏幕上的?视图渲染的三大过程我们大致了解,但是视图渲染远不止于此:这里用一个常见的硬件加速过程来表征Vsync调度:很多同学的一个误区是vsync每16ms就会有一次,但实际上vsync需要调度,不调度不会回调;消息调度:主要是Doframe消息调度,如果消息被阻塞,会直接造成卡顿;输入处理:触摸事件处理;动画处理:动画师动画执行和渲染;视图处理:主要是视图相关的遍历和三大流程;measure,layout,draw:View三大流程的执行;DisplayList更新:view硬件加速后的drawop;OpenGL指令转换:绘图指令转换为OpenGL指令;instructionbufferexchange:将OpenGL指令交换到GPU内部执行;数据处理;layersynthesis:surfacebuffersynthesisscreendisplaybuffer过程;光栅化:将矢量图形转换为位图;显示:显示控制;bufferswitching:切换屏幕上显示的帧缓冲区;Google将此过程分为:其他时序/VSync延迟、输入处理、动画、测量/布局、绘图、同步和上传、命令问题、交换缓冲区。也就是我们常用的GPU严格模式,其实道理是一样的。至此,我们回答了第二个问题:16ms内需要完成什么?准确的说,还可以进一步细化:16ms内完成APP端数据的生产;sf层的合成View在16ms内完成,视觉效果是通过这整个复杂的环节一步步展现出来的。有了这个前提,可以断定,上述任何一种链接卡顿都会导致卡顿。2.3生产者和消费者让我们回到Vsync的话题。消费Vsync的两方是App和sf,其中App代表生产者,sf代表消费者,两者传递的中间产物就是surfacebuffer。具体来说,producers可以大致分为两类,一类是window代表的页面,也就是我们平时看到的viewtree的集合;另一种是以视频流为代表,可以直接和Surface完成数据交换的来源,比如摄像头预览等。对于一般的生产者和消费者模式,我们知道会存在相互阻塞的问题。比如生产者速度快而消费者速度慢,或者生产者速度慢而消费者速度快,都会导致整体速度变慢,造成资源浪费。所以Vsync的协同作用和双缓冲甚至三缓冲的作用就体现出来了。想一个问题:缓冲区越多越好吗?过多的缓冲会导致什么问题?答案是它会导致另一个严重的问题:lag,响应延迟。结合这里的视图生命,我们可以把两个过程结合起来,让我们的视角更上一层楼:2.4机制保护这里我们来回答第三个问题。从系统的渲染架构来看,机制保护有几个方面:Vsync机制的协调;多缓冲设计;提供地面;同步屏障的保护;支持硬件绘图;解决卡顿。为了提供更流畅的体验,一方面,我们可以加强系统的机制保护,比如FWatchDog;另一方面,我们需要从应用的角度出发,管理应用中的卡顿问题。2.5看卡顿的原因经过上面的讨论,我们得出了卡顿分析的一个核心理论支撑:渲染机制的任何异常都会导致卡顿。那么,下面我们就来一一分析,看看有哪些原因可能会导致卡顿。2.5.1渲染过程Vsync调度:这是起点,但是调度过程会经过线程切换和一些委托逻辑,可能会造成卡顿,但一般可能性比较小,我们基本无法干预;消息调度:主要是doframeMessage的调度,也就是一个普通的Handler调度。如果本次调度被其他Message阻塞导致延迟,则不会直接触发后续所有流程。这里为直播建立了FWtachDog机制,可以通过优化消息调度达到插帧的效果,让界面更加流畅;input处理:input是Vsync调度中最先执行的逻辑,主要处理输入事件。如果大量事件堆积或在事件分发逻辑中加入大量耗时的业务逻辑,会导致当前帧持续时间延长,造成卡顿。抖音基础技术同学也尝试过事件采样方案,减少事件处理,取得了不错的效果;动画处理:主要是animator动画的更新,同样,动画过多,或者动画更新中存在比较耗时的逻辑,也会导致当前帧的渲染卡顿。动画的帧数减少和复杂度降低实际上解决了这个问题;视图处理:主要是接下来的三个过程,过度绘制、频繁刷新、复杂的视图效果是造成这里卡顿的主要原因。比如我们平时说的降低页面层级,主要是为了解决这个问题;measure/layout/draw:视图渲染的三大流程,因为涉及到遍历和高频执行,这里涉及到的耗时问题会被淘汰。放大,比如我们将无法调用draw、newobjects等耗时函数;DisplayList的更新:这里主要是canvas和displaylist的映射,一般不会出现卡住的问题,但是可能会出现映射失败导致的显示问题;OpenGL命令转换:这里主要是将canvas命令转换成OpenGL命令,一般是没有问题的。但是,这里有一点可以探讨。会不会有特殊类型的canvas指令消耗大量转换后的OpenGL指令,导致GPU损耗?有知识的同学可以讨论;bufferexchange:这里主要是指将OpenGL指令集交换给GPU,一般与指令的复杂度有关。一个有意思的地方是,这个曾经被用作在线收集GPU指标的数据源,但是由于多缓冲的因素导致数据准确性不够,所以被废弃了;GPU处理:顾名思义,这里就是GPU对数据的处理,主要是耗时与任务量和纹理复杂度有关。这就是我们减少GPU负载以帮助减少卡顿的原因;layercomposition:这里主要是layercompose的工作,一般是接触不到的。偶尔发现sf的vsync信号延迟,导致buffer供给不及时,目前还不清楚原因;rasterization/Display:这里暂时忽略,底层系统行为;Bufferswitching:主要针对屏幕的显示,这里的buffer个数也会影响帧的整体延迟,但是是系统行为,无法干预。2.5.2视频流除了上述渲染过程造成的卡顿外,还有其他因素,典型的是视频流。渲染卡顿:主要是TextureView渲染,textureview和window共用一个surface,每一帧都需要一起渲染并相互交互,UI卡顿会导致视频流卡顿,视频流卡顿有时也会导致UIStuck;decoding:解码主要是将数据流解码成surface可以消费的buffer数据,是除了网络之外最重要的耗时点。现在我们一般使用硬解,它的性能比软解高很多。但是帧的复杂度、编码算法的复杂度、分辨率等都会直接导致解码时间的延长;OpenGL处理??:有时候解码后的数据会处理两次,如果这样比较耗时,会直接导致渲染卡顿;网络:这个就不细说了,包括DNS节点选择、cdn服务、GOP配置等;推流异常:这个是数据源的问题,这里暂且从用户端的角度出发,暂且不讨论。2.5.3SystemLoadMemory:内存不足会直接导致GC增加甚至ANR,是造成卡顿不可忽视的因素;CPU:CPU对卡顿的影响主要是线程调度慢,任务执行慢,资源竞争慢,降频等会直接导致应用卡顿;GPU:看渲染过程对GPU的影响,其实也会间接影响功耗和发热;功耗/发热:功耗和发热一般是不分开的,高功耗会导致高热量,进而引起系统保护,比如降频、散热缓解等,间接导致卡顿。2.6卡顿的分类,我们这里将它们作为一个整体进行梳理和分类。为了更加完整,我们还将推送流放在这里。在一定程度上,我们遇到的所有滞后问题都可以在这里找到理论依据,这也是指导我们优化滞后问题的理论支撑。3.如何评价卡顿3.1在线指标3.2离线指标Diggo是字节自研的开放式开发调试工具平台。是集“评估、分析、调试”为一体的一站式工具平台。内置性能评估、接口分析、卡顿分析、内存分析、崩溃分析、实时调试等基础分析能力,为产品开发阶段提供有力助力。4.如何优化卡顿4.1常用工具4.1.1在线工具4.1.2离线工具4.2常用思路针对UI卡顿,我们手握卡顿优化的8大轴,无敌:离线代码;减少处决次数;异步;拆散;暖身;自己做不到就不做,能少做就少做,能早做就早做,能晚做就晚做,能做就让别人做做吧,能做10次就做一次,实在不行再考虑自己干大活。下面是一些常见的优化思路。请注意,这不是全部。如果有其他好的优化思路,可以一起交流。4.3已经完成的一些事情4.3.1解决UI卡顿导致的卡顿直播是SurfaceView切换的长期专项工程,分多个阶段逐步实现全直播SurfaceView,场景覆盖秀场直播聊天室、游戏直播、电商直播、媒体直播等,业务在渗透率、停留时长等方面都有比较显着的收益,耗电收益也很可观。这里有一个权衡的问题,是否可以对等SurfaceView兼容性问题pk带来的好处。一般来说,业务场景越复杂,收益越大。4.3.2解决消息调度FWatchDog基于MessageQueue的调度策略和同步屏障原理。它以平均帧时间作为判断丢帧的阈值,然后主动在MessageQueue中插入同步屏障,保证渲染异步消息和doframes的优先执行。具有渲染插帧的效果,同时具备ANR自动恢复同步屏障的能力,保证驱散的有效性。所以FWatchDog和Scatter是好搭档,可以产生1+1大于2的效果。4.3.3减少执行次数一个典型的应用场景是滑动场景的GC抑制,可以显着提升用户体验上上下下的经历。这种场景相信每个业务都会存在,尤其是遍历逻辑非常多,优化效果明显。4.3.4代码离线一些老框架、无用逻辑、低存在性代码都可以离线。这里与基础业务强相关,就不举具体的例子了。4.3.5解决耗时函数(打散/异步)首先是打散,直播做了很多任务的拆分打散,第一个可以降低当前渲染帧的耗时压力,而二是可以结合FWatchDog实现插帧效果。其实这里也可以控制任务的执行优先级,包括队列跳转等,总之需要对MessageQueue进行合理的调度。异步应用比较多。一个埋日志的框架和一些inflateloading可以用异步来解决卡顿问题。4.3.6预热直播提供预热框架,让直播内部的一次性成本逻辑在主播端执行,并提供完整的队列优先级管理、同步异步管理、任务生命周期周期管理,降低直播成本。内部首次加载的滞后问题。4.3.7硬件加速提升硬件的运行性能,如CPU主频、GPU主频、线程绑定大核、网络相关调优等,从底层提升应用的运行体验。