自 2013 年以来,由于出色的 Emscripten 项目,在网络浏览器中使用 libsodium 成为可能。
从那时起,引入了 WebAssembly。在 Web 浏览器中运行最初不是用 JavaScript 编写的代码的更有效方法。
libsodium 在 2017 年增加了对 WebAssembly 的一流支持。在支持它的网络浏览器上,以及在允许它的允许上下文中,这带来了不错的速度提升。与 JavaScript 一样,相同的代码可以在多个平台上无缝运行。
与 JavaScript 一样,应用程序开始在服务器端使用 WebAssembly。仍然像 JavaScript,并且忽略运行时实现中的错误,它不允许不受信任的代码在沙箱之外读取或写入内存。仅此一点就使其成为应用程序插件、功能即服务服务、智能合约等的引人注目的选择。
wasm32-wasi
2019 年, libsodium 添加了对新 WebAssembly 目标 () 的支持,这使得即使没有 JavaScript 引擎也可以在 Web 浏览器之外使用该库。
到今天为止,多个运行时支持wasm32-wasi
,但在同一个平台上,相同的代码可以在不同的运行时以非常不同的性能运行。
wasm32-wasi
因此,libsodium 中添加了基准测试功能。
事实证明,该基准比微基准更能代表现实世界的性能。当然,libsodium 是一个加密库。但是被测量的原语的多样性运用了 WebAssembly 运行时/编译器/JIT 实现(或未实现)的绝大多数优化,而这个基准证明是真实世界应用程序的一个很好的代表。
自推出以来,libsodium 基准已被运行时广泛使用以改进其优化管道,被研究人员广泛用于衡量实验对 WebAssembly 的影响,并被用户广泛用于为他们的工作负载选择最佳运行时。
但自从这里公布结果以来已经有一段时间了。同时,运行时间有所改善,因此更新已过期。
自上次基准测试以来发生了什么
- InNative仍在积极维护,但仍未获得
WASI
支持。虽然它看起来值得进行基准测试,但不幸的是,这暂时仍然是一个阻碍。 - Lucet 已停产,因为 Wasmtime 和 Wasmer 使用相同的代码生成器提供相同的功能。
- Fizzy似乎不再被积极维护。
- WAVM似乎也不再维护。WAVM 在其他人之前实施提案,并且一直是最快的运行时间。EPIC Games 放弃 WebAssembly 了吗?FAASM使用的分支不断进行小更新,但上游存储库中没有更多活动。这对 WebAssembly 社区来说是一个巨大的损失。
- SSVM 变成了WasmEdge,来自 Cloud Native Computing Foundation 的运行时。该项目非常活跃,从第一天起就专注于性能。它具有很多功能,包括运行插件的能力。
- Wasm3的发展步伐似乎已经放缓。然而,它仍然是唯一可以轻松嵌入任何项目的 WebAssembly 运行时,占用空间最小,解释器性能惊人。它在该类别中仍然没有任何竞争。
- Wasmtime很快从 0.40 版本升级到 3.0.1 版本,版本 4 即将到来。每个版本都是更新 Cranelift 的机会,它基于代码生成器。最近对 Cranelift 进行了很多改进,所以现在是时候看看它们如何反映在基准测试中了。
- Wasmer不断发布独特的工具和功能,例如生成独立二进制文件的能力。他们的单通道编译器也得到了更新。让我们来测试一下吧!
- Wamr看到了一堆新版本。预构建的二进制文件现在也可用。这是个好消息,因为过去的编译过程很繁琐。
- Wazero是一种新的、零依赖的 Go 运行时。虽然它还没有达到 1.0 版,但看起来非常有前途。
- Node 对 WebAssembly 和
WASI
. 然而,后者在默认情况下仍未启用,并且需要命令行标志 (--experimental-wasm-bigint --experimental-wasi-unstable-preview1
)。 - Bun
node
作为基于 JavaScriptCore 的现代替代品出现。它不支持WASI
开箱即用,但wasmer-js可以完美地模拟它。 - GraalVM现在包括对 WebAssembly 的实验性支持。
大多数其他运行时项目(Asmble、Wac、Windtrap、Wagon、Py-Wasm ……)似乎已被放弃。
将 C 代码编译为 WebAssembly
WebAssembly 包含多项提高性能的建议,例如执行尾调用优化的能力、支持 128 位向量、线程和大容量内存操作 (/ memcpy
) memset
。
不幸的是,所有这些有趣的添加仍然只是建议,运行时可能会或可能不会实现,并且可能会或可能不会默认启用。支持这些建议之一编译的应用程序将在不支持或未启用它的运行时崩溃。
理想情况下,每个 WebAssembly 应用程序都应该针对多个目标(wasm32-wasi-generic
、wasm32-wasi-generic+simd128
、wasm-wasi-generic+simd128+threads+tail_calls
等)进行分发。但这是难以管理的,运行时甚至没有实现自动选择合适目标的能力。
因此,在实践中,要编写与各种运行时兼容的应用程序,使用这些建议不是一种选择。批量内存操作可能是个例外:绝大多数运行时都支持它们,并且在wasm3
、wasmedge
、wasmer
和中默认启用。wasm2c
node
wasm32-wasi
为了对上述运行时进行基准测试,我们需要为WebAssembly 风格构建库。
为了做到这一点,并且由于 libsodium 是用 C 语言编写的,我们必须使用一个 C/C++(交叉)编译器,它可以针对wasm32-wasi
:zig cc
来自Zig工具链的命令。
以前,编译 libsodium 需要wasm32-wasi
将编译器设置为zig cc --target=wasm32-wasi
命令。但是库源代码现在包含一个 Zig 构建文件,可以用作autotools
. 因此,对于此基准测试,库是使用以下命令构建的:
zig build \
-Dtarget=wasm32-wasi \
-Drelease-fast \
-Denable_benchmarks=true \
-Dcpu=generic+bulk_memory
使用的 Zig 版本是0.11.0-dev.863+4809e0ea7
,libsodium 是修订版58ae64d319246e5530c
。
除了库本身,生成的zig-out/bin/
文件夹还包含每个基准测试的 WebAssembly 文件。
方法
测试输出它们运行所花费的时间。因此,基准测试忽略了各个运行时可能具有的设置/拆卸时间。同样,提前编译器所需的编译时间也有意不测量。我们只衡量实际执行性能。
基准测试在Scaleway提供的 Zen 2 CPU 上运行。实例上没有其他任何东西在运行,测试固定在单个 CPU 内核上,每个测试运行 200 次以进一步降低噪音。
在运行之前,WebAssembly 文件使用wasm-opt -O4 --enable-bulk-memory
命令(工具的一部分binaryen
)进一步优化。
可以在此处找到用于基准测试的确切 WebAssembly 文件集。
以下运行时已经过基准测试:
iwasm
,它是(“WebAssembly micro runtime”)包的一部分WAMR
——从其存储库下载的预编译文件wasm2c
,包含在用于引导编译器的 Zig 源代码中wasmer
3.0,使用他们网站上显示的命令安装。3个后端已经过单独测试wasmtime
4.0,从源码编译node
18.7.0 通过 Ubuntu 包安装bun
0.3.0,通过他们网站上的命令 show 安装wazero
来自 git rev796fca4689be6
,从源代码编译
不幸的是,GraalVM 无法参与基准测试,因为它对 的支持WASI
似乎非常有限。不需要random_get()
生成随机数是一个障碍。它也不支持大容量内存操作。下一次吧?
测试也按类别分组。与之前的基准迭代相比,这显着提高了可读性。
结果已中位数归一化。X 轴表示与中值性能相比,运行时要慢多少 ( 1
)。因此,越小越好,结果意味着2
运行时间比中位数慢 2 倍。
AEAD基准
经过身份验证的加密测试具有一个使用不同参数多次调用(针对每个输入块)的小函数。性能通常取决于自动矢量化和寄存器分配。
似乎没有参赛者能够对任何内容进行自动矢量化,因此结果非常相似。
认证基准
这些测试基于 SHA-2(SHA-256、SHA-512、SHA-512/256)哈希函数的软件实现。
与 AEAD 一样,它们依赖于使用不同参数多次调用的函数。但是,还有很多常量,编译器有机会内联。
在这些测试中,wasmedge
与使用 LLVM 后端的 Wasmer 相比,表现不佳,考虑到它们都是基于 LLVM 的事实,这是出乎意料的。
JavaScriptCore
以 为代表的引擎在bun
这些测试中显示出很大的改进空间。它似乎缺少单通道编译器所具有的优化通道。
盒子基准
这些测试结合了按位运算和算术。结果非常相似,除了无法承受昂贵优化的单遍编译器。
椭圆曲线上的算术
很多算术运算,大多使用64位寄存器。不出所料,结果与盒测试相似。
哈希基准
BLAKE2B 和 SHA-2 散列。这类似于身份验证基准。
bun
仍然比其他运行时慢,即使 BLAKE2B 不像 SHA-2 那样使用大型常量表。在哈希函数中肯定有一种模式还JavaScriptCore
不能正确优化。
密钥交换和密钥派生基准
引擎盖下的哈希函数相同,但这些函数的输入有很大不同:这里的输入很短。忽略单遍编译器,结果非常接近。
变质标杆
显然这里没有太多的优化空间:除了常见的例外,所有运行时的性能几乎完全相同,Wazero 除外。
这些测试执行内存分配并需要大量随机数(通过WASI
系统调用获得)。这可能是导致 Wazero 如此缓慢的原因,而不是代码是如何优化的。
一次性认证基准
这是对 Poly1305 函数的基准测试。这是使用 64 位寄存器的简单算术。多遍编译器似乎应用了相同的优化;单程编译器也是如此。
密码哈希基准
密码散列函数依赖于不可预测位置的内存读写。除了 C 编译器已经完成的工作之外,可能没有太多需要优化的地方。
迪菲-赫尔曼基准
有限域上的算术。ADCX/ADOX
优化机会包括进位传播的使用。
总的来说,所有运行时都表现得很好,Wazero 除外,它比使用 singlepass 后端的 Wasmer 慢得多。
Secretbox基准测试
在这里,我们使用 salsa20、chacha20 和 poly1305 对经过身份验证的加密进行基准测试。最适合 32 位和 64 位寄存器的简单算术。
注册机基准
这些测试都是关于随机性提取的。这里没有太多需要优化的地方。WASI
这些测试主要测量调用获取随机字节的开销,并且很可能测量WASI
一般调用的开销。
node
和之间的区别bun
是预期的,因为node
对 具有本机支持WASI
,而bun
需要wasmer.js
仿真层。应该不会花很长时间bun
也包括本机WASI
支持,这应该使它与其他运行时处于相同的范围内。
wasmedge
, 但是,没有WASI
仿真借口。调用外部函数wasmedge
可能比使用其他运行时有更多的开销。
签名基准
椭圆曲线的算术运算,触发常见的优化过程,并进行很少的WASI
系统调用。在这里,wasmedge
也基于设法比后端LLVM
更快。wasmer
LLVM
公用事业基准
编解码器、比较函数和其他简单的辅助函数,旨在有意阻止编译器优化,作为使它们保持恒定时间的最大努力。
正如预期的那样,跨运行时的性能非常相似。
流密码基准
Salsa20 和 Chacha20 流密码,因此,主要是涉及 32 位值向量的按位算术。有自动矢量化的机会,但这并非微不足道。
性能非常相似,单通道编译器除外。
累积基准
iwasm
,wasmer
(LLVM 后端)并且wasmedge
都用于LLVM
代码生成。直觉上,人们可能期望非常相似的性能。但这种情况并非如此。
但是iwasm
,这个基准测试中的新人(至少在 AOT 模式下)一直是最快的运行时间。这是一个惊喜。它的性能很可能与 类似wavm
,可以考虑作为其替代品。
话虽这么说,wasmer
非常接近。
在该LLVM
类别中,考虑到该项目从一开始就非常注重性能,我希望能wasmedge
带头。结果有点令人失望。V8
(由 表示node
)大约一样快。
另一方面,用于cranelift
代码生成的运行时(wasmer
使用该后端,并且wasmtime
)执行几乎相同。
缩小一点,真正令人印象深刻的是LLVM
基于的结果和cranelift
基于的结果有多接近。
这是正确的。代码cranelift
生成器已经变得和LLVM
. 考虑到这是一个相对年轻的项目,由一个非常小(但显然非常有才华)的团队从头开始编写的事实,这是非常令人印象深刻的cranelift
。
JavaScriptCore
以 为代表的引擎令人bun
失望。作为一个 JavaScript 引擎,它的速度非常快。作为 WebAssembly 引擎,还没有那么多。其中一些结果可以用缺少本机WASI
实现来解释,但可能还有更多优化空间。
wasmer
与singlepass
后端的wazero
共同点是它们以流式传输的方式非常快速地编译。对于一些涉及不可信输入的应用程序来说,这是一个关键属性。
另一方面是无法进行昂贵的优化。这是一个不可避免的权衡。
wazero
的结果还不是很好。但由于它与 Go 语言的无缝集成涉及额外的限制,尤其是在寄存器数量有限的 Intel CPU 上。 wazero
在 ARM CPU 上可能表现得更好,这是我们很快就会测量的东西。
无论如何,该wazero
团队非常重视性能,并且已经开始调查这些结果。
与本机代码的比较
WebAssembly 与本机代码相比如何?
上图表示使用最快的 WebAssembly 运行时运行单个测试所花费的时间与将库编译为本机代码并进行特定于体系结构优化的相同测试之间的比率。
隐藏了两个异常值:aegis128l
和aegis256
测试。本机代码利用本机 CPU 指令来计算 AES 轮次。另一方面,WebAssembly 无法利用这些指令,必须退回到缓慢的软件实现。
结果,这些基于 AES 的测试在运行 WebAssembly 时比本机代码慢 80 倍。这并不代表大多数应用程序。然而,它强调了 WebAssembly 对依赖 AES 的加密操作的真正限制。
忽略这一点,当使用最快的运行时时,WebAssembly 仅比具有特定架构优化的本机代码慢 2.32 倍(中位数)。不错!
判决书
我们有四类 WebAssembly 运行时:
- 口译员,在那个类别中,
wasm3
可能仍然是最好的选择。它也比其他任何东西都更容易嵌入。 LLVM
//具有可比性能的基于运行时Cranelift
。V8
JavaScriptCore
基于运行时——略微落后于其余的。- 单程编译器。
iwasm
如果您正在寻找性能最佳的产品,iwasm
目前是您的不二之选。
iwasm
是WAMR
项目的一部分。
与其他选项相比,它令人生畏。
它感觉就像一个厨房水槽,包括不同的组件(IDE 集成、“应用程序框架库”、远程管理、SDK),这使它看起来像是对简单问题的复杂解决方案。文档也有点混乱和繁琐。
它在编译时和运行时也有很多旋钮。内存使用调整页面很吓人。我希望只有一个max_memory
旋钮,而不是多个设置,这些设置可能会或可能不会工作,具体取决于应用程序及其编译方式。也就是说,这也适用于其他运行时,默认值可能是合理的。
忽略这一点,有一个简单的 C API,以及 Python 和 Go 的绑定。所以在这些语言中,其实是相当好用的。
运行时本身非常小 (50 KB),内存占用也很小。它可以根据应用程序进行定制,以进一步减少它,使其成为受限环境的绝佳选择。特别是因为它开箱即用地支持 Zephyr、VxWorks、NuttX 和 RT-Thread 等平台。
基于 LLVM/Cranelift/V8 的运行时
node
, wasmtime
,wasmedge
和wasmer
在同一个球场。
它们都还没有稳定的 API。对于 Rust API 尤其如此。wasmer
如果您的应用程序依赖于或的 Rust API wasmtime
,请做好维护成本的准备。
然而,C API 要稳定得多:wasmtime
的 C API 最近只有一个重大变化,同时wasmer
彻底改造了一次 API,并从那以后一直保持稳定。
C wasmedge
API 非常不同,并且具有更多的特性。重大更改仍然会发生,但它们通常很小且易于处理。
node
(或者,实际上,V8
)拥有迄今为止最稳定的 API,尽管WASI
对 的支持node
仍被认为是实验性的。
因此,node
(或V8
+uvwasi
当嵌入时)是一个很好、安全、保守的选择,对于 WebAssembly,切换到其他东西并没有太多的性能提升。是的,和其他人一样,V8
也支持完整的模块 AOT 编译。
但是,为了运行 WebAssembly 代码,V8
必须运送整个引擎。这是一个巨大的依赖。对于还需要支持 JavaScript 的应用程序来说这不是问题,但对于只需要 WebAssembly 的应用程序来说这绝对是大材小用。
这就是wasmtime
,wasmedge
发挥wasmer
作用的地方。它们更小(= 减少攻击面),并包含适合运行第三方代码/智能合约的功能,例如燃气计量。
对于大多数用户而言,这三个运行时之间没有显着差异。它们具有相似的功能(例如 AOT 编译)并以相同的方式运行代码,速度大致相同。
不过,它们具有一些独特的功能,可以对某些应用程序产生影响。
wasmer
拥有最大的生态系统,其库使得许多编程语言以及 PostgreSQL 和 Visual Studio Code 等应用程序都非常容易使用。
它还包括为所有支持的平台生成独立二进制文件的能力,有一个包管理器等等。尽管有一些重大变化,但它似乎更注重 API 稳定性而不是替代方案,这使其成为计划长期维护的应用程序的合理选择。
wasmedge
是来自 CNCF 的运行时,以及 Docker 用来运行带有 WebAssembly 应用程序的容器。因此,即使这不是您选择的运行时,也强烈建议测试您的 WebAssembly 代码是否在其上正常运行,因为它的受欢迎程度可能会飙升。
wasmedge
带有网络支持,尽管目前仅限于 Rust 和 JavaScript 应用程序。但是它最好的特性之一wasmedge
是它的插件系统,允许它在不改变核心运行时的情况下进行扩展。wasi-nn
开箱即用,它带有实现和建议的插件wasi-crypto
,以及一个 HTTP 客户端插件和一种以受控方式运行外部命令的简单方法。
wasmedge
包括可轻松将其嵌入到 C、Rust、Go、JavaScript 和 Python 应用程序中的库。它支持抢占(这很好!),指令计数和气体测量/限制,
已经分配了wasmtime 中相当多漏洞的 CVE 。有些很严重(在自由、越界访问、类型混淆之后使用……)可能导致秘密泄露和任意代码执行。
这是一件坏事吗?其实并不是。据我所知,V8
和JavaScriptCore
是wasmtime
迄今为止唯一公开披露漏洞的 WebAssembly 运行时。
这非常令人惊讶,尤其是因为其中一些漏洞存在于 中cranelift
,而wasmer
.
wasmtime
在每个漏洞发生后进行负责任的安全披露,并竭尽全力防止类似漏洞再次发生。
小心翼翼地进行更改,并且不断对项目进行模糊测试以发现新的错误。
如果意图是在浏览器环境之外运行任意的、不受信任的代码,wasmtime
感觉是最安全的选择。
libwasmtime
在没有任何可选功能的情况下,即使使用 AOT 且未调用编译函数,最小可执行链接的权重也只有 22 MB。
wasmer
这比(重量为 16 MB 的基本独立可执行文件)多一点wasmer
,但远小于,它只附带一个重量不小于 45 MB 的共享库,与库(46 MB)wasmedge
一样大!node
总而言之,很难强烈推荐这些运行时之一。使用它们中的任何一个都不会出错,除非您的应用程序需要它们的独特功能之一。
JavaScript核心
JavaScriptCore
is to Safari
what V8
is to Chrome
:一个可移植的、功能齐全的 JavaScript 引擎,由优秀的多级解释器和编译器支持。
两者都用于我们今天使用的绝大多数 Web 浏览器,但也在node
( V8
) 和bun
( JavaScriptCore
) 中的浏览器之外,以及功能即服务服务和扩展应用程序的方式。
而且JavaScriptCore
性能非常出色,经常超过其中之一V8
。
在他们现有的编译器之上构建,他们最终都获得了对 WebAssembly 的支持。
不幸的是,JavaScriptCore
目前的表现不如V8
WebAssembly。截至今天,如果您正在寻找 JavaScript+WebAssembly 引擎,我会推荐V8
.
话虽这么说,JavaScriptCore
开发人员联系了我,要求将许可证文件添加到基准文件中。因此,他们很可能会修复JavaScriptCore
当前缺少的优化,并且引擎最终会表现得和其他运行时一样好,甚至更好。
Wazero
将 WebAssembly 模块嵌入到 Go 应用程序中,wazero
应该是您的首选,除非性能绝对至关重要。
wazero
完全用 Go 编写,零依赖。这具有许多优点:安全性、可移植性、与语言的完美集成以及将其添加到现有项目中是微不足道的,与替代方案相比,增加的开销可以忽略不计。
wasm2c
现在是介绍的时候了wasm2c
,这显然属于不同的类别。
WebAssembly 编译器是否可以包含 WebAssembly 运行时以运行其自身的 WebAssembly 版本以再次编译自身?如果这听起来令人困惑,请阅读Zig 如何摆脱 C++ 编译器。
wasm2c
是一个 WASM 到 C 的转译器。transpiler 本身是在几天内编写的,非常简单,适合约 2000 行独立代码,并且不会试图变得聪明或优化任何东西。它只是将单个 WebAssembly 操作码逐字翻译成非常愚蠢的 C 代码,并让 C 编译器负责所有优化工作。它还包括对WASI
.
该wabt
软件包包括一个类似的工具,具有更高级的功能。但是看看最简单的实现能做什么似乎很有趣。
除了 C 编译器之外,还需要零依赖项(在这里,我使用zig cc
它是因为它已经安装)。运行时开销是……零:没有运行时!内存开销也可以忽略不计。但是性能呢?
看看这个基准测试的结果,这种微不足道的方法的性能非常出色。事实上,它在每项测试中都与最快的 WebAssembly 运行时并驾齐驱。
但有一个陷阱。WebAssembly 的两个主要卖点是可移植性(毕竟这是字节码),以及保证应用程序无法访问任意内存位置。
从 的输出编译的代码wasm2c
提供了前者:WebAssembly 代码的单个副本可以在多个平台上运行。
但是,它目前没有采取任何措施来强制执行后者。这样做需要在堆栈之前和线性内存段之后添加保护页,或者额外的操作,例如对加载/存储操作中使用的指针应用掩码,以将它们保持在安全范围内。
但是,当要运行的 WebAssembly 代码是可信的时,这种非常紧凑和简单的方法工作得非常好,并且是引导编译器的绝佳决定。
利用这种方法,w2c2看起来是一个非常有前途的项目,值得关注(声称只比本机代码慢 7%!)
大家好!
在过去的两年中,WebAssembly 运行时世界发生了很多事情。项目被放弃了,但新项目出现了,令人兴奋的新功能已添加到现有运行时,并且已经取得了令人印象深刻的性能改进(现在与 一样cranelift
快LLVM
)。不再有明显的赢家,只有好的选择。
我们还看到了 WebAssembly 在浏览器环境之外的新应用,以及跨编程语言使用 WebAssembly 的一致方式(Extism)。WebAssembly 也成为机密计算的重要组成部分(Vera Cruz、Enarx、Inclavare)。
语言和工具中的 WebAssembly 支持也得到了改进。Swift 现在可以发出 WebAssembly 代码。C/C++ 代码可以编译为WASI
usingzig cc
而不是手动修补clang
或必须安装另一个完整的LLVM
. tinygo
变得更加成熟。以 WebAssembly 为目标的新编程语言诞生了。
至于在网络浏览器中使用 WebAssembly 等,Cowasm是开源的:由 SageMath 开发的 Emscripten 的替代品,具有实时协作功能。仅此一项就可以解锁很多可能性。
WebAssembly 生态系统的未来绝对是光明的!