WebAssembly
WebAssembly起初的目标主要是补充JavaScript, 后来逐渐发展成一种标准化的通用运行时.
尽管在发展过程中不断有人试图用WebAssembly取代JavaScript, 但多年过去, 距离其取代JavaScript仍然遥远.
尽管在发展过程中不断有人试图用WebAssembly取代JavaScript, 但多年过去, 距离其取代JavaScript仍然遥远.
WebAssembly导出的函数在JavaScript将被视作native code.
WebAssembly起初雄心勃勃, 但其兑现的承诺远不如预期, 标准推进缓慢, 整体上看是令人失望的.
- 内存模型非常简单, 以至于它甚至无法销毁掉申请的内存, 内存只能增长而不能缩小:
https://github.com/WebAssembly/design/issues/1397 - wasm相比真正的本机代码仍有很多的性能损失(本机代码的速度是wasm的1.75倍以上).
https://nickb.dev/blog/wasm-and-native-node-module-performance-comparison/ - 虽然WebAssembly支持将i64类型转换为BigInt(JavaScript不支持i64),
但WebAssembly本身并不支持BigInt.
因此, JavaScript无法直接传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
由于JavaScript引擎在JIT方面的持续优化, 以及类型转换开销和数据复制开销的普遍存在, JavaScript经常能快过单线程WASM程序.
这导致通过WASM来提升JavaScript性能的尝试有时会变得非常尴尬, 因为WASM的开发成本几乎总是比JavaScript高, 最终收益却可能为负.
通过经验来提前判断程序是否值得通过WASM优化因此变得很有意义.
这导致通过WASM来提升JavaScript性能的尝试有时会变得非常尴尬, 因为WASM的开发成本几乎总是比JavaScript高, 最终收益却可能为负.
通过经验来提前判断程序是否值得通过WASM优化因此变得很有意义.
经验表明, 除了少数执行时间较长的CPU密集型任务, 大部分任务都不适合用WASM重写.
当出现以下任一特征时, 都可以视作是危险信号, 暗示着编写WASM程序可能是不值得的:
- WASM程序出现超过一轮JavaScript互操作.
- WASM程序出现超过一轮序列化/反序列化.
- 将CPU密集型任务视作一个函数, JavaScript可以在500ms以内完成一次该函数的调用.
JavaScript与WASM之间的互操作非常低级, 只支持传递数值类型(整数和浮点数), 不支持字符串等常见的高级类型.
当需要传递高级类型的数据时, 需要将高级类型序列化到缓冲区, 然后传递对应的指针, 由需要的一方将其反序列化.
举例来说, 字符串传递时传递的不是字符串, 而是传递共享缓冲区的指针和长度, 再由需要字符串的一方将其识别为字符串.
举例来说, 字符串传递时传递的不是字符串, 而是传递共享缓冲区的指针和长度, 再由需要字符串的一方将其识别为字符串.
WebAssembly的内存指的是一块分配给WebAssembly的线性内存空间, 其本质是一块可增长的连续无符号字节缓冲区.
"线性内存"又称作"平面内存模型(Flat Memory Model)", 这被认为是接口最简单, 性能最好, 复杂度最小的内存模型.
"线性内存"又称作"平面内存模型(Flat Memory Model)", 这被认为是接口最简单, 性能最好, 复杂度最小的内存模型.
JavaScript可以共享给WebAssembly可调整大小的ArrayBuffer.
像wasm-bindgen这样的工具会自动抽象线性内存来平滑JavaScript与WebAssembly的访问.
像wasm-bindgen这样的工具会自动抽象线性内存来平滑JavaScript与WebAssembly的访问.
在WebAssembly里, 像函数指针这样的引用出于安全性/可移植性/稳定性等原因, 不能存储在线性内存空间中.
作为替代, 这类不可存储在线性内存中的数据被存储在"表(table)"里, 目前只有函数引用被存储在表里, 未来可能增加新的元素, 比如线程.
作为替代, 这类不可存储在线性内存中的数据被存储在"表(table)"里, 目前只有函数引用被存储在表里, 未来可能增加新的元素, 比如线程.
全局变量是在JavaScript侧创建的, 由WebAssembly使用的变量, 在多个WebAssembly模块之间可以共用.
https://github.com/WebAssembly/component-model
https://github.com/WebAssembly/WASI
一个建立在WASM上的, 用于系统编程的I/O接口.
本质上, 它是为了创建一个专用于WASM的系统库, 使WASM无需通过胶水代码进行系统编程, 以减少跨边界开销.
本质上, 它是为了创建一个专用于WASM的系统库, 使WASM无需通过胶水代码进行系统编程, 以减少跨边界开销.
如果WASI能够流行, WASM会发展为一种公共的中间语言, 有希望彻底颠覆整个程序设计领域的生态环境:
编程语言不再以平台为编译目标开发程序, 而是只以WASM为编译目标来开发程序.
编程语言不再以平台为编译目标开发程序, 而是只以WASM为编译目标来开发程序.
这是WASM自己的垃圾回收提案, 用于在不移植虚拟机的情况下将依赖于GC的编程语言移植到WASM上.
这降低了让编程语言支持WASM的难度, 更好地支持WASM的平台无关性.
这降低了让编程语言支持WASM的难度, 更好地支持WASM的平台无关性.
最受欢迎的WASM运行时.
第二受欢迎的WASM运行时.
WebAssembly的文本格式, 语法基于S-表达式, 人类可读, 文件扩展名为
.wat.WAT被视为高级语言的编译目标, 相当于WASM的中间语言/汇编语言.
不仅可以把WAT编译成WASM, 还可以从WASM反编译回WAT.
不仅可以把WAT编译成WASM, 还可以从WASM反编译回WAT.
参考:
- https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Understanding_the_text_format
- https://webassembly.github.io/spec/core/text/index.html
最受欢迎的WebAssembly语言, 大部分WASM程序都是通过Rust编写的.
Rust生态环境中主要的WebAssembly CLI.
wasm-pack build 的默认输出结果是整个npm包, v0.11.2以后可以通过 --no-pack 选项阻止此行为.JavaScript代码生成器, 当需要使用
js-sys, web-sys 等JavaScript环境的绑定时, 需要使用此项目.https://github.com/rustwasm/wee_alloc
一个可选的WASM内存分配器, 注重于删除不必要的Rust基础设施来减小WASM模块体积, 但会影响运行速度.
对于只在初始化时进行动态内存大小分配, 之后就没有再进行分配的程序有用.
缺乏维护, 存在内存泄漏问题, 会导致程序速度变得越来越慢:
一个不自以为是的WebAssembly CLI.
用Rust的WASM前端Web应用框架.
有很多stars, 但性能不理想, 即使相比React都还有相当大的距离:
https://github.com/yewstack/yew/issues/5
https://github.com/yewstack/yew/issues/5
Rust侧, JavaScript环境的绑定.
Rust侧, Web环境的绑定.
由于绑定是通过JavaScript完成的, 访问Web API的速度不会比JavaScript快.
对
js-sys / web-sys 的重新封装, 提供更好的人体工学.受欢迎程度仅次于Rust的原生WebAssembly语言, 但市场份额被Rust挤占得相当厉害, 份额从2022年开始大幅下降.
与TypeScript的区别:
- AssemblyScript只有
null, 没有any和undefined. - 不支持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