Prime Video用WebAssembly优化8000种设备更新

4 阅读6分钟

Prime Video如何为超过8000种设备类型更新其应用

转向WebAssembly提升了稳定性和速度

在某机构Prime Video,要向超过8000种设备类型(如游戏主机、电视、机顶盒和USB流媒体棒)上的数百万客户提供内容。每当需要进行更新时,每种设备都需要单独的原生发布,这就在可更新性和性能之间构成了艰难的权衡。

在过去一年中,一直使用WebAssembly (Wasm) 来帮助解决这一权衡问题。Wasm是一个允许用高级语言编写的代码在任何设备上高效运行的框架。为了助力Wasm生态系统,某机构已加入Bytecode Alliance,这是一个致力于在Wasm等标准基础上构建安全、高效、模块化、可移植运行时环境的联盟。

通过在Prime Video应用的某些元素中使用Wasm替代JavaScript,中端电视上的平均帧时间从28毫秒降至18毫秒。最坏情况下的帧时间也从40毫秒降至25毫秒。在持续进行的工作中,正在进一步降低帧时间。

分工

为了在广泛设备上实现高效更新同时保持性能,Prime Video应用分为两部分:用C++编写的高性能引擎(存储在设备上)和每次应用启动时下载的易于更新的组件。

在原始架构中,设备上的内容是一个轻量C++层,包含JavaScript虚拟机(VM)和运行Prime Video应用所需的组件,负责处理输入、媒体管道以及网络访问、图像解码、窗口事件处理等过程。

下载的内容(运行时)包括应用代码,以及处理场景管理、动画系统、图形渲染、布局和资源管理等底层组件。历史上,这些组件都使用JavaScript。

这种架构拆分使得能够交付新功能和错误修复,而无需经历更新C++层这一非常缓慢的过程。可下载代码通过完全自动化的持续集成和交付管道交付,可以每隔几小时就发布更新。然而,在编写高性能代码(C++)和编写可轻松更新但性能较低的代码(JavaScript)之间始终存在张力。

WebAssembly

Wasm为比JavaScript更具表现力的编程语言(如C或Rust)提供了编译目标。与JavaScript代码一样,编译后的Wasm二进制文件在VM上运行,该VM在代码和硬件之间提供统一接口,与设备无关。

Wasm最初是为Web浏览器设计的,但现在浏览器之外已有独立的Wasm VM应用,例如运行物联网软件、游戏模组和服务器端工作负载。

Wasm研究始于2020年8月,当时构建了一些原型,在模拟底层JavaScript组件所做工作类型的场景中比较Wasm VM和JavaScript VM的性能。在这些实验中,用Rust编写并编译为Wasm的代码比JavaScript快10到25倍。

然而,无法直接将Prime Video应用用Rust重写并在Wasm VM上运行,因为它仍需要在不支持Wasm的旧设备和浏览器上运行。也不想只为新架构创建新应用,因为重视在不同环境中部署相同的应用。

这就是为什么仅将底层系统从JavaScript迁移到Wasm。通过这种方式,仍能为应用带来性能提升,而应用团队无需知道或关心某些系统是在Wasm VM上运行的。

新架构

Wasm二进制文件与JavaScript代码一起部署,通过相同的全自动管道,可以在几小时内将程序从代码提交运行到客户设备上。

切换过程

新架构中,Wasm VM和JavaScript VM在不同线程中运行。那么如何在不重写应用的情况下从第一种架构过渡到第二种架构?

第一步是更新设备上的内容以包含Wasm VM,这样它现在可以运行给定软件组件的两个版本(仅JavaScript 或 JavaScript和Wasm)。这使得能够逐步向部分客户发布Wasm组件。

必须修改Prime Video应用与这些组件的通信方式。在高层,应用通过创建场景(视觉场景的表示)来工作,场景由节点组成,这些节点的实现是设备特定的。宿主节点(如视图、图像、文本)是一个数据结构,包含更新和渲染视觉场景组件所需的所有信息。

在启动时,会检查是否在支持Wasm的设备上运行。如果是,则在JavaScript中创建轻量级宿主节点,这些节点除了向Wasm VM发送命令外不做任何其他事情。当这些命令被处理时,"真正的"宿主节点在Wasm中创建。

使用消息在两个VM之间进行通信,因为不希望JavaScript VM的工作中断Wasm VM的工作。Wasm组件的工作是更新节点并尽可能快地将帧输出到屏幕,不受任何中断。

困难之处在于以保留JavaScript系统行为的方式进行这种切换。有时不得不在新的Wasm版本中复制JavaScript渲染器的"错误"行为,因为应用在某些边缘情况下依赖它。确保JavaScript VM代码永远不会在错误的线程上调用任何危险函数也增加了额外的困难。

结果

如前所述,转向Rust和Wasm提高了应用的帧率稳定性和速度。为了实现可靠的每秒60帧生成并改善输入延迟,会将更多系统(如焦点管理和布局)迁移到Wasm。

Wasm VM的总内存消耗(包括模块实例、环境和模块本身)最多为7.5兆字节。通过将这些系统迁移到Wasm,总共节省了30兆字节的JavaScript堆内存。内存是大多数部署设备上的稀缺资源,因此这是一个受欢迎的减少。

Wasm模块的二进制大小在压缩后为150千字节(未压缩750千字节,符号剥离后)。模块的小尺寸加上快速的VM启动时间,意味着添加Wasm不会影响应用启动时间。

使用Rust使得各个经验水平的程序员都能贡献代码,而无需要求审阅者仔细检查每一行是否存在安全陷阱。信任编译器,可以将代码审查重点放在功能上,而不是语言边界情况。

使用Rust的另一个好处是能够访问高质量库的生态系统。例如,构建了一个使用egui(一个Rust GUI库)在应用场景渲染上叠加调试器信息的应用。将egui与Wasm渲染器集成只花了几个小时的工作,并提供了一种简单的方式来深入了解引擎的内部结构。

总的来说,认为对Rust和WebAssembly的投资已经得到回报:在编写了37,000行Rust代码一年后,显著提高了性能、稳定性和CPU消耗,并减少了内存使用。FINISHED