WebAssembly

WebAssembly的目标并非取代Web上的JavaScript, 而是与JavaScript互相补充.
WebAssembly导出的函数在JavaScript被视作使用native code的函数.

  • 由于需要从JavaScript侧暴露外部函数给WASM调用, 加上数据类型的转换/序列化/复制成本,
    在WASM侧调用Web API的效率 比JavaScript慢得多, 调用的次数越多越慢, 非数值类型数据传递得越多越慢.
    这也导致了WASM的最佳使用方法是用WASM补充JavaScript的计算性能, 而不是完全由WASM编写程序.
    在未来, 通过externref/anyref和更好的WASM接口设计, 有望显著改善WASM调用Web API的速度.
  • 内存模型非常简单, 以至于它甚至无法销毁掉申请的内存, 内存只能增长而不能缩小:
    https://github.com/WebAssembly/design/issues/1397
  • 尽管wasm很快, 但相比真正的本机代码, 它还是有很多的性能损失(本机代码的速度是wasm的1.75倍以上).
    https://nickb.dev/blog/wasm-and-native-node-module-performance-comparison/
    在少数情况下, wasm可能会比优化过的JavaScript代码还慢.
    根据Zaplib的测试, 在实际使用中wasm往往只比预热过的JavaScript代码快一倍, 因为实际项目并不总是大量使用CPU密集型操作.
  • 虽然WebAssembly支持将i64类型转换为BigInt(JavaScript不支持i64), 但WebAssembly本身并不支持BigInt.
    因此, JavaScript无法传BigInt作为参数, 如果要处理大整数, 则被迫传字符串, 然后由WebAssembly这一侧将字符串转换为自己的BigInt实现,
    这无疑会造成很多开销.
  • 当前导入WASM模块的方式不是跨平台的, Web和Node.js使用不同的导入API.
    有望在WASM支持作为ECMAScript模块使用时得到改进.
  • 只支持32位, 可利用的最大内存为4G.
    64位可能会在未来支持.
  • 文件体积很大, 一个用Rust编译出来的WASM文件至少也有10KB.
  • 无法零复制共享内存, 这使得很多高性能任务无法高效率完成, 例如发送ArrayBuffer或共享SharedArrayBuffer.
    https://github.com/WebAssembly/design/issues/1162
    https://github.com/WebAssembly/design/issues/1231
  • WASM的多线程目前处于提案状态.
    https://github.com/WebAssembly/threads

WASM采用了一种相当奇怪的交互方式, JavaScript与WASM之间的调用只支持传递数值类型(整数和浮点数), 不支持字符串等常见的高级类型.

这导致两者之间需要传递高级类型的数据时, 实际上需要共享一个缓冲区, 然后传递对应的指针(即缓冲区内的偏移量), 再由需要的一方将其视作对应的高级类型来处理.
举例来说, 字符串传递时传递的不是字符串, 而是传递共享缓冲区的指针和长度, 再由需要字符串的一方将其识别为字符串.

WebAssembly的内存指的是一块分配给WebAssembly的线性内存空间, 其本质是一块可增长的连续无符号字节缓冲区.
"线性内存"又称作"平面内存模型(Flat Memory Model)", 这被认为是接口最简单, 性能最好, 复杂度最小的内存模型.

JavaScript可以共享给WebAssembly可调整大小的ArrayBuffer.
像wasm-bindgen这样的工具会自动抽象线性内存来平滑JavaScript与WebAssembly的访问.

在WebAssembly里, 像函数指针这样的引用出于安全性/可移植性/稳定性等原因, 不能存储在线性内存空间中.
作为替代, 这类不可存储在线性内存中的数据被存储在"表(table)"里, 目前只有函数引用被存储在表里, 未来可能增加新的元素, 比如线程.

全局变量是在JavaScript侧创建的, 由WebAssembly使用的变量, 在多个WebAssembly模块之间可以共用.

https://github.com/WebAssembly/WASI

一个建立在WASM上的, 用于系统编程的接口.
本质上, 它是为了创建一个对任何可以编译到WebAssembly的语言都可以使用的系统库.

如果WASI能够流行, WebAssembly会发展为一种公共的中间语言, 编程语言不再以平台为编译目标开发程序, 而是只以WebAssembly为编译目标来开发程序.
毫无疑问, 这将彻底颠覆整个程序设计领域的生态环境.

一种以S表达式表示的WebAssembly格式.

可以从wat文件生成wasm文件.
一般将其视作WebAseembly的"汇编代码".

一种以二进制表示的WebAssembly格式.

理所当然的, 体积相比wat格式较小, 因此是主要被使用的格式.

最受欢迎的WASM运行时.

第二受欢迎的WASM运行时.

最受欢迎的WebAssembly语言, 大部分WASM程序都是通过Rust编写的.

Rust生态环境中主要的WebAssembly CLI.

wasm-pack build 的输出结果将是整个npm包.
然而, 即使考虑到WASM模块在当前标准下的导入具有平台相关性, 以及WebAssembly需要JavaScript主动暴露函数的现实, 这也是非必要的.
此外, wasm-pack还提供了 wasm-pack loginwasm-pack publish 方法用于在npm发布包.
wasm-pack的这一系列与npm强耦合的设计是相当糟糕的, 很容易让人怀疑开发团队的水平, 并且暴露出wasm-pack的非必要性.

  • https://github.com/rustwasm/wasm-pack/issues/691
  • https://github.com/rustwasm/wasm-pack/issues/811
  • https://github.com/rustwasm/wasm-pack/pull/695

JavaScript代码生成器, 当需要使用 js-sys, web-sys 等JavaScript环境的绑定时, 需要使用此项目.

https://github.com/rustwasm/wee_alloc

一个可选的WASM内存分配器, 注重于删除不必要的Rust基础设施来减小WASM模块体积, 但会影响运行速度.

对于只在初始化时进行动态内存大小分配, 之后就没有再进行分配的程序有用.

一个由于不自以为是, 从而比wasm-pack好得多的WebAssembly CLI.

用Rust的WASM前端Web应用框架.

有很多stars, 但性能不理想, 即使相比React都还有相当大的距离:
https://github.com/yewstack/yew/issues/5

Rust侧, JavaScript环境的绑定.

Rust侧, Web环境的绑定.

js-sys / web-sys 的重新封装, 提供更好的人体工学.

受欢迎程度仅次于Rust的原生WebAssembly语言, 但市场份额被Rust挤占得相当厉害, 份额从2022年开始大幅下降.

与TypeScript的区别:

  • AssemblyScript只有 null, 没有 anyundefined.
  • 不支持union, 只支持泛型.
  • 不支持对象字面量和数组字面量.
  • AssemblyScript使用自己的标准库.
  • 访问宿主环境需要宿主配合, 无法直接访问.
  • 非null数组的成员需要手动初始化后才能访问.
  • AssemblyScript支持运算符重载, 顶级装饰器.
  • 数组排序基于数值而不是字符串.
  • 缺乏反射, 包括 Object.keys 这样的方法无法在AssemblyScript里支持.

特点:

  • API与JavaScript高度相似.
  • 允许手动申请和回收堆内存.
  • 支持动态数组和静态数组.
  • 自动内存管理.
  • 缺少像Rust的wasm-bindgen这样的东西, WASM与JavaScript代码的沟通依赖于手动编写声明和设置导出, 用户体验极差.
  • 不支持传递任意JavaScript对象至AssemblyScript.
    https://github.com/AssemblyScript/assemblyscript/issues/1620
    在某种程度上, WASM anyref/externref特性的支持能解决此问题, 但该特性在AssemblyScript里仍处于实验阶段.
  • 相比Rust这样成熟的语言, 性能较差.
    在一些基准测试里, 差别可以扩大到2倍.
  • AssemblyScript还未实现闭包.
    https://github.com/AssemblyScript/assemblyscript/issues/798