Introduction | The AssemblyScript Book

走进 WebAssembly 的世界 - 字节前端的专栏 - 掘金

1. 前言

在本文中,我们将基于一个实际的客户端生产环境中遇到的问题,来观察如何使用 WebAssembly 技术打破原有的性能瓶颈,提升用户价值。在这个过程中,我们将引入 AssemblyScript 技术栈,把原有的 TypeScript/JavaScript 逻辑下沉 WebAssembly 中,从而实现性能的大幅提升,并通过实验验证具体的性能收益。

2. 背景

在我们看直播时,经常可以看见由用户送礼触发的炫酷礼物特效,比如抖音一号、嘉年华等等。在早期直播的礼物特效多以播放 MP4 为主,为此我们也自研了 AlphaPlayer 的方案。客户端通过 AlphaPlayer 进行播放,设计侧只需要生产 MP4 资源即可,基本上属于业界比较常规的特效播放方案,比较适合高频迭代的常规礼物。与此同时,该方案的瓶颈也很明显:难以支持交互类特效、受资源包体积约束无法做到大量排列组合的随机性特效。

由于 MP4 无法实现具有定制化,随机性与交互性强的礼物特效,直播营收侧引入了一种新的特效方案:基于 WebGL 的跨端渲染方案 (用 Web 技术栈快速构建 Native 视图的高性能跨端框架,下文中用 “字节跨端框架” 代替) 。万象烟花就是由该方案实现的礼物特效之一。

图 1. 万象烟花礼物特效

图 1. 万象烟花礼物特效

在字节跨端框架的环境中,该特效使用公司自研的基于 JavaScript 的渲染引擎 “Sar Engine”。由于该引擎在需求开发阶段尚未具备通用的 GPU 粒子系统。因此,烟花粒子系统的属性都使用 CPU 计算更新,需要在 JavaScript 层处理粒子的位移、大小、颜色等,从而带来了不小的性能负担。

相对于 C++ 实现的渲染引擎,JavaScript 的低运行效率会严重影响渲染效果。通过抓帧和性能压测分析,如图 2 所示,可以观测到性能的瓶颈在 CPU 上。因此,需要采用一些手段进行优化以提高性能。在烟花特效中将部分重复且重 CPU 计算的逻辑转换为 WebAssembly 进行调用便是其中的重要优化之一。

图 2. iPhone 7 JavaScript 版本烟花性能表现

图 2. iPhone 7 JavaScript 版本烟花性能表现

3. AssemblyScript 在字节跨端框架中的应用

在上文中提到使用 WebAssembly 对字节跨端框架环境中的 JavaScript 代码进行优化,那么我们首先要做的便是探索如何将已有的 JavaScript 代码编译成 WebAssembly 产物,可以在该环境中加载并运行。AssemblyScript[1] 可以帮助我们快速将业务代码中的 TypeScript 代码转换成可编译成 WebAssembly 产物的格式,结合框架环境,其主要步骤如下

图 3. 字节跨端框架应用 AssemblyScript 优化的步骤

图 3. 字节跨端框架应用 AssemblyScript 优化的步骤

由上图可见,编译工具和业务代码是独立的,可以在本地编译好 WASM 产物,在任何地方使用。关于 AssemblyScript 的开发环境搭建,可以参考教程 [1],推荐使用官方教程的方式编译。下文中会详细介绍 AssemblyScript 的整个使用过程。

3.1 安装依赖与构建

我们需要克隆 AssemblyScript 的仓库,在本地安装完依赖并运行,使本地具有将 AssemblyScript 编译成 .wasm 产物的能力, 步骤如下:

git clone <https://github.com/AssemblyScript/assemblyscript.git>
cd assemblyscript
npm install
npm link
npm run dev # 打包 dist,不然会找不到 asc

按上述步骤执行后就完成了整个安装,后面可以通过 asc 命令进行编译。但通常我们不使用 asc 手动编译单个文件,而是通过 asbuild 命令自动编译,并同步生成胶水代码。

3.2 初始化项目

完成依赖安装后,我们可以执行 npx asinit . 在本地初始化一个 AssemblyScirpt 项目。这个项目可以放在实际的业务工程中,也可以放在其他地方,因为我们最终需要的仅仅是由该项目产生的接口文件与 .wasm 产物。

执行命令后会生成一些项目初始文件,其中以下两个是比较重要的:

# 编写AssemblyScript的位置,我们在此export的接口,在编译后都会在 .wasm 产物中提供接口./assembly/index.ts
# 用于编写测试 .wasm 代码的地方,我们可以在此处引入 .wasm 产物,然后使用 JavaScript 代码调用进行测试./tests/index.js

3.3 编译

在我们写好 AssemblyScirpt 代码后,使用以下命令进行编译: