WebAssembly (Wasm)是基于堆栈的虚拟机的二进制指令格式,一种低级汇编语言,旨在非常接近已编译的机器代码,并且非常接近本机性能。前面在文章《快速认识 WebAssembly》介绍过一些,本文再次介绍其优势和使用场景,通过代码体验WebAssembly项目开发的过程。
简单地说,Wasm
是一个编译目标,可以使用大约 30 种语言编写的代码,使用特定于 WebAssembly 的工具来编译它,将其编译为 .wasm
文件,目前最流行的针对 Wasm
的语言是 C
、C++
和 Rust
(即因为它们自己管理内存并且不需要垃圾收集器)。对 Go
、Python
和 JavaScript
生态系统的支持也在快速增长。编译生成的.wasm
文件可以在浏览器或服务器上。Wasm
文件包含虚拟机可以读取的二进制指令,并且由于 Wasm
以虚拟机为目标,因此它适用于许多芯片架构,它以流行硬件的最小公分母为目标,堆栈机,这是它区别于其他产生二进制代码的目标的地方。
Wasm
最初是为浏览器构建的,但是随着技术的成熟,在服务器端看到了越来越多的用例。本文再次介绍 WebAssembly 的优势及应用场景,并通过示例认识其项目开发的过程,点击查看代码。
拥有什么优势
Wasm 允许使用熟悉的语言编写代码并在任何地方运行它。
更快的启动时间
在服务器上,Wasm
可以实现比 Docker
容器快 10-100
倍的冷启动时间,因为它不需要为每个容器创建一个 OS 进程。在浏览器中,解码 Wasm
比解析、解释和优化 JavaScript 更快,因此 Wasm
代码在浏览器中的执行速度比 JavaScript 更快。
近乎原生的性能
关于 Wasm
的性能细节存在一些争议,但它的优势在于允许用户将其应用程序的计算密集型部分封装到较低级别的语言。 Wasm
的许多性能优势来自于它(它是 Wasm 代码)被构建为尽可能接近本机机器代码这一事实。
轻量级
Wasm
二进制文件体积小,因此只使用少量带宽,通常比浏览器中的交叉编译 JavaScript 花费更少的时间通过网络传输。
便捷通用
任何 Wasm
运行时都可以运行任何 Wasm
代码(尽管并非所有运行时都支持所有 Wasm
扩展,即不同的 WASI
接口类型)。大多数浏览器都支持 WebAssembly,并且在服务器端(WasmEdge、Wasmtime 等)有许多运行 Wasm 代码的运行时。鉴于浏览器和服务器(以及硬件)对 Wasm
的广泛支持,它是具有可移植的,并且也非常通用,大约 30 种语言可以编译或在其中执行(C、C++、Rust、Python、Go、AssemblyScript、JavaScript等等)。
安全
WebAssembly 安全模型的两个目标是:(1)保护用户免受错误和或恶意模块的侵害;(2)为开发人员提供开发安全应用程序所需的原语。在这个程度上,Wasm
的范围是有限的,在 Wasm 运行时中运行的代码是内存沙盒和功能受限的。
以上几点使它对客户端和服务器应用程序都很有趣。在客户端,有一个世界(部分)由于 Wasm
,浏览器最终成为所有应用程序运行的默认操作系统。在服务器上,Wasm
有可能成为下一个默认的容器系统。Docker 对虚拟机所做的事情,Wasm
也会对 Docker 做。正如 Fermyon 的 Matt Butcher 所说:
如果说 VM 是云计算的重量级,而容器是中级, 那么 WebAssembly 是轻量级的完美选择。
应用场景
Wasm 将提供快速且安全的客户端和服务器应用程序,有哪些应用场景呢?
加速WEB应用程序
根据 Figma 用例,使用 Wasm,可以将应用程序的性能/计算密集型部分用 JavaScript 编写,然后将 JavaScript 换成性能更高的语言,例如 Rust/C/C++
。但情况并非总是如此。
一切都是(Web)应用程序
一次编写,到处运行,WebAssembly 希望实现这个最初由 Sun Microsystems 创造的与 Java 有关的术语的梦想。这在实践中并不是一个简单的壮举,但 Wasm 绝对可以更轻松地将应用程序带到以前原生的 Web(和其他平台)上。Photoshop 和 Autodesk Web 就是很好的例子。
插件
Wasm
非常适合在隔离的沙箱中执行不受信任的代码。一旦大多数平台规模化,最终会构建插件系统,使最终用户能够构建与其平台交互的定制软件。通过在这个插件系统中使用 Wasm
,平台可以让他们的用户以任何语言构建插件,而不必担心让用户执行不受信任的代码的安全风险,因为该代码是沙盒的。Wasm
的所有其他好处也在这里发挥作用:速度、小型二进制文件和快速加载。默认情况下,每个插件系统都希望高性能、安全且易于使用,而 Wasm
帮助实现了这一目标。
新容器系统
正如上面所述,Wasm
具有受约束的安全模型,它是跨操作系统的,具有快速的冷启动时间,具有出色的性能,不需要为每个容器创建新的操作系统进程,并且占用空间非常小。这些都是可以替代 Docker 的新型容器系统的有吸引力的特征。正如 Solomon Hykes 在推文所说,WASI
是真正推动这个新容器系统向前发展的缺失环节。
包管理器
WebAssembly 将有一个包注册表和管理器。WAPM 是第一个尝试这个的人,一旦 WASI 和组件模型无处不在,就会有许多令人信服的理由来解释为什么要使用 Wasm 包管理器。
游戏
在浏览器中,WebAssembly 可能很棒,原因与它对性能密集型 Web 应用程序非常有用:让它们在 Web 上高效运行。根据 WebAssembly 文档,示例包括需要快速启动的轻量级游戏、资产密集型 AAA 游戏和点对点游戏。同样根据 WebAssembly 文档介绍,它可以在服务器上用于创建游戏分发服务,使游戏可移植且安全。
区块链
人们一直在谈论 Wasm 作为 EVM 的替代品,Parity Ethereum Client 在 Wasmi 中运行 Wasm 字节码,这使得 Wasm 代码能够访问区块链并与之交互。另一个很好的例子是 ewasm,目前正在研究它作为 EVM1 的替代品(来源)。它旨在让开发人员使用 WebAssembly 与以太坊区块链进行交互,从而支持更多语言。
不受信任代码的服务器端计算
类似于插件系统的用例是不受信任代码的服务器端计算。许多平台最终都会公开自己的系统,让最终用户在其平台上编写代码,例如 Airtable Scripts。使用 Wasm,像 Airtable 这样的平台可以让其用户在 Airtable Scripting 平台上编写以多种语言编写的函数。
无服务器计算
无服务器函数是 WebAssembly 的完美场景,Wasm 的沙盒、性能、快速启动时间和语言支持使其成为运行无服务器函数的完美技术。
机器学习
随着实时工作的物联网/连接设备变得越来越流行,能够执行实时机器学习将变得至关重要。像 WasmEdge 这样的运行时使这成为可能。
Hello World
本文将使用 AssemblyScript 来构建 “Hello World” ,创建项目目录 wasm-hello
,执行一下命令:
npm init
初始化完成之后继续执行:
npm install --save-dev assemblyscript
安装后,编译器提供了一个方便的脚手架实用程序来快速设置一个新项目,在当前目录中:
npx asinit .
asinit
命令会自动创建推荐的目录结构和配置文件:
./assembly
:存放编译为WebAssembly的AssemblyScript源的目录。./assembly/tsconfig.json
:TypeScript配置继承了推荐的AssemblyScript设置。./assembly/index.ts
:项目入口文件./builds
:构建工件目录,其中存储已编译的WebAssembly文件。./build/.gitignore
:./asconfig.json
./package.json
./tests/index.js
./index.html
接下来,创建一个 scripts
来存放 JavaScript 的代码文件,创建文件 main.js
,一个使用 WebAssembly Web API 加载 Wasm 模块的函数:
const wasmBrowserInstantiate = async (wasmModuleUrl, importObject) => { let response = undefined; if (!importObject) { importObject = { env: { abort: () => console.log("Abort!"), }, }; } // 检查浏览器是否支持流实例化 if (WebAssembly.instantiateStreaming) { // 获取模块,并在下载时实例化它 response = await WebAssembly.instantiateStreaming( fetch(wasmModuleUrl), importObject ); } else { const fetchAndInstantiateTask = async () => { const wasmArrayBuffer = await fetch(wasmModuleUrl).then( (response) => response.arrayBuffer() ); return WebAssembly.instantiate(wasmArrayBuffer, importObject); }; response = await fetchAndInstantiateTask(); } return response; };
接下来通过上述函数来加载实例化 wasm
模块,从 Wasm 模块调用导出的 add()
函数:
const runWasm = async () => { // 实例化wasm模块 const wasmModule = await wasmBrowserInstantiate("./build/core.wasm"); // 从wasm 调用 add 函数 const addResult = wasmModule.instance.exports.add(24, 24); // 将函数执行结果加入到 DOM中 document.getElementById( "result" ).innerHTML = `Hello World! addResult: ${addResult}`; }; runWasm();
回到项目目录下的 html 文件,代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Hello World - AssemblyScript</title> </head> <body> <div id="result"> </div> <script src="./scripts/main.js"> </script> </body> </html>
启动程序:
npm start
就可以看到效果,注意最新版本要升级 node 环境到版本 16 以上。github上的代码为更加复杂的示例(来自官方),运行的效果如图: