亚马逊Prime Video中使用WebAssembly提高了效率


亚马逊基于通过Prime Video通过 8,000 多种设备类型向数百万客户提供内容,如游戏机、电视、机顶盒和 USB 供电的流媒体。当想要进行更新时,这些设备中的每一个都需要单独的本机版本,从而在可更新性和性能之间做出艰难的权衡。
在过去的一年里,亚马逊一直在使用 WebAssembly (Wasm),这是一个允许用高级语言编写的代码在任何设备上高效运行的框架,以帮助解决这种权衡。
亚马逊加入了字节码联盟,该联盟致力于开发基于 Wasm 等标准构建的安全、高效、模块化和可移植的运行时环境。
通过对 Prime Video 应用程序的某些元素使用 Wasm 而不是 JavaScript,将中档电视的平均帧时间从 28 毫秒减少到 18 毫秒。最坏情况下的帧时间也从 40 毫秒减少到 25 毫秒。正在进一步降低。
为了在各种设备上实现高效更新,同时仍然保持性能,Prime Video 应用程序有两个部分:

  • 一个用 C++ 编写的高性能引擎,存储在设备上:薄的 C++ 层,其中包括一个 JavaScript 虚拟机 (VM) 和运行 Prime Video 应用程序所需的组件,这些组件处理输入、媒体管道以及诸如网络访问之类的过程、图像解码和窗口事件处理。
  • 一个易于更新的组件,每次都下载应用程序启动:包括应用程序代码,以及处理场景管理、动画系统、图形渲染、布局和资源管理等的低级组件。

但是,在编写高性能代码 (C++) 和编写性能较差但我们可以轻松更新的代码 (JavaScript) 之间一直存在矛盾。
 
WebAssembly
Wasm 为提供比 JavaScript 更具表现力的编程语言(例如 C 或 Rust)提供了编译目标。与 JavaScript 代码一样,编译后的 Wasm 二进制文件在 VM 上运行,该 VM 提供代码和硬件之间的统一接口,而与设备无关。
Wasm 最初是为 Web 浏览器设计的,但现在在浏览器之外有 Wasm VM 的独立应用程序,例如运行物联网软件、游戏模组和服务器端工作负载。
然而,亚马逊不能只用 Rust 重写 Prime Video 应用程序并在 Wasm VM 上运行它,因为它仍然需要在不支持 Wasm 的旧设备和浏览器上运行。我们也不想只为新架构创建新应用程序,因为我们重视跨环境部署相同的应用程序。
这就是我们的新架构的样子:

带有 WebAssembly 的新架构。
Wasm 二进制文件与 JavaScript 代码一起部署,通过相同的全自动管道,可以在几个小时内将程序从代码提交到在客户的设备上运行。
开关
上图显示了新架构,其中一个 Wasm VM 和一个 JavaScript VM 在不同的线程中运行。但是我们如何在不重写应用程序的情况下从第一个架构过渡到第二个架构?
第一步是更新设备上的内容以包含 Wasm VM,因此它现在可以运行给定软件组件的两个版本(仅 JavaScript 或 JavaScript 和 Wasm)。这使我们能够逐步将 Wasm 组件发布给一部分客户。
我们必须修改 Prime Video 应用程序与这些组件的通信方式。在高层次上,应用程序通过创建场景来工作——视觉场景的表示——它由实现特定于设备的节点组成。主机节点(例如,视图、图像、文本)是一种数据结构,它具有更新和渲染视觉场景组件所需的所有信息。
在启动时,我们检查我们是否在有 Wasm 可用的设备上运行。如果是这样,我们会在 JavaScript 中创建轻量级主机节点,除了向 Wasm 虚拟机发送命令之外什么都不做。处理这些命令时,会在 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 KB(在符号剥离后为 750 KB 未压缩)。模块体积小,加上 VM 启动时间快,意味着 Wasm 的加入不会影响应用启动时间。
使用 Rust 使所有经验水平的程序员都可以贡献代码,而无需审阅者仔细检查每一行是否存在安全隐患。我们信任编译器,我们可以将代码审查的重点放在功能上,而不是语言的极端情况。
总体而言,我们认为对 Rust 和 WebAssembly 的投资得到了回报:经过一年的 37,000 行 Rust 代码,我们显着提高了性能、稳定性和 CPU 消耗并降低了内存利用率。