小鹏的技术博客

求索

Hi,我是小鹏,Java/iOS/Android开发者!


希望在这里记录一些自己对技术的学习和思考,欢迎交流!

直播间性能优化实践

一、直播间结构

在线教育场景下的直播间不同于泛娱乐类直播App,其业务的复杂度更高,同时用户的设备分布更分散,有很多低端机及性能差的设备,而用户的上课的时长通常在1.5小时左右,长时间的停留让设备发热及耗电明显,最终导致直播间的性能问题成为了根本的瓶颈。

要解决直播间的性能问题,首先要先从直播间的整体构成入手,具体如下图:

直播间

从整体UI的结构上看整个直播间是横屏,由课件区、主讲的拉流区、用户自己的推流区、同组学员区、聊天区组成,这几个区域都是常驻,从进入直播间开始就存在,其中的课件区是Webview,上课过程中还会通过Webview展示互动题以及画板。

整体结构抽象图

其中课件区承载的是H5课件,最上层是常驻透明的笔迹画板同样是Webview承载具体是由H5的canvas绘制,课件的复杂度在于有很多转场动画和交互,画板会频繁绘制笔迹,从Webview角度看这些高复杂的场景都会导致资源消耗较大且不可控。

内容区分层

不可见的还有直播的推拉流、编解码、图像的渲染。信令通道,信令的解析、分发、排重、补偿等等,这些逻辑虽不可见,但依然是性能消耗的大户,特别是流媒体相关,一定程度上是性能的黑洞。

二、性能衡量(APM)

搞清楚了直播间的结构后,接下来我们要定量分析出性能具体如何?即衡量直播间的性能,从App角度衡量性能有现成的概念APM。通过APM来收集、上报、分析数据,产出对应的报表,来衡量性能。再根据业务场景直播间来输出结论。再进一步可以整合问题定位系统,进行问题的定位及排查,这样就完成了诊断->定位->优化的闭环。这部分的设计不展开讲,后续可以单独开一篇文章讲一下问题诊断、定位、优化及APM的设计。

APM的核心是监控CPU、内存、FPS这些指标,并按照一定的频率及采样率上报分析,最终分析出性能的整体表现。在我们的业务中分析的结果是,从进入直播间开始,运行内存200MB的占用,随时间的变化升到800MB甚至更高,低端机CPU持续占用80%~90%左右,帧率方面低端机上低帧率(低于10)的场景特别频繁。

客服和技术支持系统可以监控到用户的反馈情况,数据包含舆情、客服反馈渠道、用户社群等,可以分析出用户视角的问题。

最后,综合业务、客服、技术支持、用户体验等,最终确定按照如下指标衡量直播间性能:

  • 用户问题反馈率:图像(卡顿或画面问题)、声音、互动,结合业务目标是千分位以下;
  • 性能指标:CPU、内存、FPS,目标是平稳且及时释放;
  • 业务指标:视频卡顿率、信令(到达、接收、展示)、互动成功率,目标是99.9%;

三、明确问题

通过业务逻辑、用户视角的现象结合技术监控,进行分析、诊断,最终定位出如下核心问题:

  • 卡顿:由于性能消耗较大且持续濒临性能崩溃的边缘,造成卡顿、卡死、crash等,最经典的案例是有些低端机运行内存剩余不足10MB,CPU几乎在100%,GPU内存暴掉出现OOM;
  • 课件性能:表现是课件区内容展示不全或不连续、卡在某一页、笔迹花屏等等;
  • 黑白屏:如上面的内容区分层图所示,课件、互动题、画板三层均为Webview,移动端的Webview是由系统底WebGL ES进行渲染,渲染过程中如果出现GPU的OOM,在iOS表现为白屏(WKWebview进程触发了Terminate回调),Android平台表现为黑屏或部分黑屏;
  • 互动失败:互动题是由信令通道的信令来驱动,信令如果出现延迟到达、丢失、乱序等就会造成互动失败;

四、优化方案

从业务上的表现及技术上的分析最终确定问题核心是CPU、运行内存、GPU这些资源在调度或使用过程中出现资源不足导致,资源上的消耗、占用、竞争是问题的根本,同时资源的释放不及时也会导致问题加剧,所以从资源角度解决问题需要先从各方面进行资源的释放及管控。

独立进程

从平台特性角度首先想到的方案是Android系统的独立进程,因为独立进程有自己独立的资源调度及使用控制,在需要的时候申请相对独立的资源,在不需要的时候释放可以更彻底的回收资源。这种资源的管控恰恰适合直播间的场景,用户在进入直播间时创建独立进程,在退出时彻底销毁释放资源,过程中如果资源告警或超过阈值还可以进行销毁重建。

具体的实现是App主进程作为核心载体及宿主,将直播间加入到独立的进程中,直播间的进程与主进程建立AIDL通信通道,完成进程间的功能调用。使用MMKV在进程间共享数据,需要特别注意数据一致性(使用文件锁)。过程中由统一的调度器(Service Manager)管理所有的binder服务,包括系统服务和应用自定义的服务,具体参考下图:

独立进程架构图

当然由于系统及平台的限制独立进程在iOS是实现不了的,iOS平台需要其他方式,下面就来讲讲适用全平台的优化方案!

容器化

关于资源的管控有很多现成的模式可以参考,容器化是最为直接的方式,以容器为组织单位进行生命周期的管理,可有效的管控资源。譬如将Webview看成是一个容器,其生命周期可分为创建、加载、更新、重置、关闭、销毁的整个过程,每个过程都有其存在的价值和互相之前的关系。

  • 创建负责生成容器对象及基本的资源申请,使容器处于初始化的状态;
  • 加载负责将内容load到容器将渲染展示内容;
  • 更新与加载对应,负责将将老的内容刷新至新的状态,并重新渲染展示内容;
  • 重置可以将原有的资源释放,使容器重新恢复到初始化状态;
  • 关闭是将容器从可见状态转换为非可见状态,但此时容器的对象依然存在;
  • 销毁是彻底将容器的对象释放,并将其占用的所有资源释放。

其整个生命周期的管理基于平台提供的API进行封装,中间使用对应的桥接层进行对接,最上层使用统一调度层将核心的能力提供给上层业务进行整合和调度,具体如下图:

容器化架构图

信令通道

容器是承载内容的载体,而直播场景的内容是有一定连续性的,类似视频播放器,一个完整的视频是由一系列连续的帧图构成,通过时间的纬度将一系列帧图组织在一起,一帧帧播放出来,再配合上音频就构成了视频。容器也类似,使用信令将容器驱动起来,将一页页内容展示出来,譬如主讲老师的课件、笔迹、互动题等,课件的翻页、笔迹的书写、互动题的发起结束都是一系列的信令进行驱动的。

其中信令的通道选择尤为重要,最初信令的通道设计是由直播流的SEI通道进行承载,通过时间戳的对齐向上层业务抛信令,这样的设计最初目的是为了保证更实时的传输,但事与愿违,由于直播流的技术采用了WebRTC,它的网络层实现使用的是UDP协议,UDP本身不保证时序及可靠性,所以会有大量的乱序及丢包存在,导致信令的错乱和丢失,可靠性大打折扣。

使用SEI通道的另外一个问题是完全耦合到直播拉流的逻辑中,假如想切换拉流方式需要同时切换信令通道,带来了更多的额外成本。

所以最理想的信令通道应该是独立通道保证可靠性的方式,业界也有成熟方案——长连接,其实在直播课的业务中,rtmp拉流方式下已经使用过长连接,技术上可以保证可靠性同时也是相对独立的服务及通道。所以我们最终使用长连接通道,将信令从直播拉流中解耦出来。

渲染优化

解决了内容的承载、驱动,接下来要解决渲染的问题,业务上所有可视化的内容都涉及渲染,主要包括了Navtive UI渲染、流媒体渲染、Webview内容渲染,下面展开讲一下其中的原理及方案:

Native UI

Native的UI渲染由系统提供,对资源的使用相对合理,只要编码上保证没有内存泄漏一般不会出现问题。

流媒体

直播场景占核心地位的渲染来自流媒体,即视频流的渲染,直播视频解码与渲染的Pipline流程是视频解码 -> 视频前后处理 -> 视频的渲染,而核心的渲染流程是:

  • CPU计算需要显示的内容,通过数据总线传给GPU;
  • GPU拿到数据,开始渲染数据并保存在帧缓存区中;
  • 视频控制器会按照VSync信号逐行读取帧缓冲区的数据,经过数模转换传递给显示器显示;

视频渲染图

当前主流的视频渲染技术有OpenGL、Metal、Vulkan等,这些技术更多的是依托平台,最常见的是OpenGL,其最大优势是跨平台。Metal是苹果公司推出的,具有更好的性能。Vulkan在安卓平台,与Metal类似Vulkan可以更详细的向显卡描述你的应用程序打算做什么,从而可以获得更好的性能和更小的驱动开销。

视频渲染的过程其重要的性能消耗在于频繁的计算、拷贝内容,最直接的方案是减少计算及拷贝,在解码上使用硬解将计算逻辑由CPU分散GPU进行计算,同时使用平台更优的Metal或Vulkan方式进行渲染。但这这种方式不适用于部分低端机(GPU相对差的机型),安卓机成为突出,针对部分机型需要单独的适配,成本会比较高。但带来的收益也很明显,硬解更加省电,适合长时间的移动端视频播放器和直播,手机电池有限的情况下,使用硬件解码会更加好。减少CPU的占用,可以把CPU让给别的线程使用,有利于手机的流畅度。

低端机的渲染最有效的方案是进行降级,目前直播间所采用的是减少不必要的推拉流、减少动画、降低业务复杂度,保证核心体验等。

Webview

Webview的渲染主要消耗在于图片展示、GIF播放、DOM计算等,从资源角度入手减少资源的占用,同时让CPU、运行内存、GPU资源能及时释放。在课件及互动场景下更多的采用cocos runtime容器平替方案,效果比较显明。具体有相关的专项文章可以参考。

线程优化

除上面比较明显的资源消耗大部头之外,从CPU角度关注其核心消耗最直接有效的方式莫过于使用调试工具进行摸排。Android的可以使用adb或Android Studio提供的工具进行调试,iOS使用Instrumments进行调试。重点关注占用CPU时间片较长的线程或常驻线程,这部分在直播间场景发现比较重的是大量的常驻log线程,通过统一的log系统将其收敛管控即可。其他沉重的线程也可以使用统一的线程池管控的方案进行优化。

五、效果

内存

运行内存的优化收益明显,从是从原来的平均800MB降至平均200MB左右,并且从之前的进入直播间之后一直上升的趋势转为过程会不断回收释放,维持在相对平稳的状态。

其中独立进程对于Android机型的优化最为明显,平均可释放300MB内存。容器化对于课件和笔迹等常驻类直播间的场景优化相对更有效,内存上的表现是回收及时。

CPU和FPS

低端设备上表现最为突出,从CPU和FPS的表现上观测到,低帧率的现象明显变少。同时从舆情数据上看,卡顿反馈率降低了80%左右,其中渲染优化、线程优化、业务降级最为有效,其他用户反馈率也达到了千分位的目标。

黑白屏

  • Webview容器的黑白屏率初期降低了83%左右,经过后续的升级迭代最终趋近于0;
  • Cocos Runtime容器黑白屏率0;
  • 所有方案对此均有效果,整体资源使用的更优会让黑白屏率变得极低甚至趋近于0;

四、总结

上述就是我们对直播间性能优化所做的一些实践和积累,性能优化是一个长期的工程,需要结合自身的业务特性不断进行打磨,力求极至。

除了优化方案,良好的性能监控系统开发规范也很重要,是保障劣化的重要方式,否则性能优化方案取得的收益抵不过业务快速迭代带来的性能黑洞,结合APM及相关监控指标可以不断的升级优化措施及规范,不断的落地实践。

未来在我们可能还会在资源竞争CPU线程调度GPU渲染技术手段简化业务等方面继续深入探究性能优化,后面也会将其中的某些优化方案整理成文章继续分享,也希望各位前辈同行多多指教、探讨!

本文最初发布于InfoQ 作业帮直播间性能优化实践

更早的文章

编译器之LLVM简史

写了这么久程序你是否真的知道自己的代码是如何转换为可执行文件在操作系统上运行的?这是我最近写代码时的常常思考的问题,所以就有了这篇探究原理的文章。本文旨在记录人个对Mac OS、iOS系统编译器相关原理在学习中的理解和对相关概念的摘录,如有不妥之处欢迎指正。1. 编译器?编译器(Compiler),是一种计算机程序,它会将用某种编程语言写成的源代码(原始语言),转换成另一种编程语言(目标语言)。其中原始语言可以是C、C++、Java、Objective-C、Swift等,目标语言则通常是机...…

继续阅读