跳到主要内容

wasm 初探

本质就是二进制文件,编译好的字节码(非常接近机器码可以快速转换)

特点

  • 高效
    • WebAssembly 有一套完整的语义,实际上 wasm 是体积小且加载快的二进制格式, 其目标就是充分发挥硬件能力以达到原生执行效率
  • 安全
    • WebAssembly 运行在一个沙箱化的执行环境中,甚至可以在现有的 JavaScript 虚拟机中实现。在web环境中,WebAssembly将会严格遵守同源策略以及浏览器安全策略。
  • 开发
    • WebAssembly 设计了一个非常规整的文本格式用来、调试、测试、实验、优化、学习、教学或者编写程序。可以以这种文本格式在web页面上查看wasm模块的源码
  • 标准
    • WebAssembly 在 web 中被设计成无版本、特性可测试、向后兼容的。WebAssembly 可以被 JavaScript 调用,进入 JavaScript 上下文,也可以像 Web API 一样调用浏览器的功能。当然,WebAssembly 不仅可以运行在浏览器上,也可以运行在非web环境下

wasm

wasm

编译 wasm 的工具

  • AssemblyScript:支持直接将 TypeScript 编译成 WebAssembly。这对于前端来说,入门的门槛很低的。

  • Emscripten:可以说是 WebAssembly 的灵魂工具。将其他的高级语言,编译成WebAssembly。

  • WABT:将 WebAssembly在字节码和文本格式相互转换的一个工具,方便开发者去理解这个 wasm 到底是在做什么事。

性能对比

WebAssembly版本和原生JavaScript版本的递归无优化的 (斐波那契数列)Fibonacci 函数,下图是这两个函数在值是45、48、50的时候的性能对比。

wasm

JavaScript API

方法

  • WebAssembly.compile()
  • WebAssembly.instantiate()
  • WebAssembly.validate()

  • WebAssembly.Module
  • WebAssembly.Instance
  • WebAssembly.Memory
  • WebAssembly.Table
  • WebAssembly.CompileError
  • WebAssembly.LinkError
  • WebAssembly.RuntimeError

具体流程

// WebAssembly.compile() 装载 wasm
Promise<WebAssembly.Module> WebAssembly.compile(bufferSource);

// WebAssembly.Module 转换为 module
var myModule = new WebAssembly.Module(bufferSource);

// WebAssembly.instantiate() 实例化
Promise<ResultObject> WebAssembly.instantiate(bufferSource, importObj) // ResultObject:{module, instance}


Promise<WebAssembly.Instance> WebAssembly.instantiate(module, importObject);

// WebAssembly.Instance
var myInstance = new WebAssembly.Instance(module, importObject);

// WebAssembly.validate() 验证

// WebAssembly.validate(bufferSource); // boolean

// WebAssembly.Memory 可用于 JavaScript 和 WebAssembly 的数据共享(不在 v8 中,自己的内存回收体系)
// 可以使用JS创建,传递给 WASM
// 可以在WASM里创建,使用JS获取

// WebAssembly.Table 可以用来在JS对象上存放WASM函数的引用

// 错误
// WebAssembly.CompileError
// WebAssembly.LinkError
// WebAssembly.RuntimeError

使用

import module from 'test.wasm' js 只能通过请求发起。webpack 5 支持导入

const importObject = {
func: {
log: (num)=> document.write(num)
}
}
fetch('test.wasm')
.then( response => response.arrayBuffer())
.then( types => WebAssembly.instantiate(bytes, importObject))
.then(({instance}) => window.test = instance.exports.test)

执行流程

这个过程是这样的

JavaScript -> WebAssembly(test) -> JavaScript(document.write())

参考链接

构建wasm方式

建议弃坑 使用WABT来编译S-表达式

推荐编译工具 Emscripten

在线方式 WasmFiddle

WebAssembly 中文网

Emscripten

使用 emsdk 构建 参考文档

# mac 平台直接执行可执行文件  window 自行百度 or ./emsdk.bat
./emsdk install latest
./emsdk activate latest

配置环境变量

# mac 平台, window 平台自行体会 emsdk_env.bat
source ./emsdk_env.sh

编译方式

1、可执行文件

gcc hello.c -o hello
# 执行
./hello

2、编译成wasm在nodejs中执行

emcc hello.c -o hello_node.js

# 生成两个文件
=> hello_node.wasm
=> hello_node.js

# 执行
node hello_node.js

3、编译成wasm在浏览器中执行

# 其中 -s WASM=1 是制定要wasm文件; -O3 优化选项 ,优化选项中优化度最低是O1,优化度最高是O3,最好推荐 O1,避免意外优化
emcc hello.c -s WASM=1 -O3 -o hello_html.html

# 生成三个文件
=> hello_html.wasm
=> hello_html.js
=> hello_html.html

# 执行
# 在浏览器中执行,注意,生成的文件要放在web服务器中然后通过 web 访问,不能直接从硬盘访问

测试 Math.c 文件

参考链接

Q & A

math.c / mathcpp.cpp

int add( int a, int b)
{
return a + b;
}

int reducer(int a, int b) {
return a - b;
}

int square(int a)
{
return a * a;
}

sh

# math.c
emcc math.c -Os -s WASM=1 -s SIDE_MODULE=1 -o math.wasm
# mathcpp.cpp
emcc math.cpp -Os -s WASM=1 -s SIDE_MODULE=1 -o mathcpp.wasm

html

<script>
/**
* @param {String} path wasm 文件路径
* @param {Object} imports 传递到 wasm 代码中的变量
*/
function loadWebAssembly(path, imports = {}) {
return fetch(path) // 加载文件
.then(response => response.arrayBuffer()) // 转成 ArrayBuffer
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
imports.env = imports.env || {}

// 开辟内存空间
imports.env.memoryBase = imports.env.memoryBase || 0
if (!imports.env.memory) {
imports.env.memory = new WebAssembly.Memory({ initial: 256 })
}

// 创建变量映射表
imports.env.tableBase = imports.env.tableBase || 0
if (!imports.env.table) {
// 在 MVP 版本中 element 只能是 "anyfunc"
imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
}
console.log(module)
// 创建 WebAssembly 实例
return new WebAssembly.Instance(module, imports)
})
}
//调用
loadWebAssembly('./mathcpp.wasm')
.then(instance => {
console.log(instance.exports)
// const add = instance.exports.add // 取出cpp里面的方法
// const square = instance.exports.square // 取出cpp里面的方法
// const reducer = instance.exports.reducer

// console.log('10 + 20 =', add(10, 20))
// console.log('3*3 =', square(3))
// console.log('10-5 =', reducer(10, 5))
// console.log('(2 + 5)*2 =', square(add(2 + 5)))
})

</script>