首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

WebAssembly 现状与实战(2)

WebAssembly 现状与实战(2)

WebAssembly                相关文件格式前面提到了 WebAssembly 的二进制文件格式 wasm,这种格式的文件人眼无法阅读,为了阅读 WebAssembly                文件的逻辑,还有一种文本格式叫 wast; 以前面讲到的计算斐波那契序列的模块为例,对应的 wast 文件如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func $src/asm/module/f (param f64) (result f64)
(local i32)
  get_local 0
  f64.const 1
  f64.eq
  tee_local 1
  if i32
    get_local 1
  else
    get_local 0
    f64.const 2
    f64.eq
  end
  i32.const 1
  i32.and
  if
    f64.const 1
    return
  end
  get_local 0
  f64.const 1
  f64.sub
  call 0
  get_local 0
  f64.const 2
  f64.sub
  call 0
  f64.add
end




这和汇编语言非常像,里面的 f64 是数据类型,f64.eq f64.sub f64.add 则是 CPU 指令。
为了把二进制文件格式 wasm 转换成人眼可见的 wast 文本,需要安装 WebAssembly 二进制工具箱 , 在 Mac 系统下可通过 brew                install WABT 安装,安装成功后可以通过命令 wasm2wast f.wasm 获得 wast;除此之外还可以通过 wast2wasm                f.wast -o f.wasm 逆向转换回去。
WebAssembly                相关工具除了前面提到的 WebAssembly 二进制工具箱,WebAssembly 社区还有以下常用工具:
  • : 能把                    C、C++代码转换成 wasm、asm.js;
  • : 提供更简洁的                    IR,把 IR 转换成 wasm,并且提供 wasm 的编译时优化、wasm 虚拟机,wasm 压缩等功能,前面提到的                    AssemblyScript 就是基于它。
WebAssembly JS                API目前 WebAssembly 只能通过 JS 去加载和执行,但未来在浏览器中可以通过像加载 JS 那样 <script                src='f.wasm'></script> 去加载和执行 WebAssembly,下面来详细介绍如何用 JS 调                WebAssembly。
JS 调 WebAssembly 分为 3 大步:加载字节码 > 编译字节码 > 实例化,获取到                WebAssembly 实例后就可以通过 JS 去调用了,以上 3 步具体的操作是:
  • 对于浏览器可以通过网络请求去加载字节码,对于 Nodejs 可以通过 fs 模块读取字节码文件;
  • 在获取到字节码后都需要转换成 ArrayBuffer 后才能被编译,通过 WebAssembly 通过的 JS API   编译后会通过 Promise resolve 一个  ,这个 module 是不能直接被调用的需要;
  • 在获取到 module 后需要通过  API 去实例化 module,获取到 Instance 后就可以像使用 JS                    模块一个调用了。
其中的第 2、3 步可以合并一步完成,前面提到的   就做了这两个事情。
1
2
3
WebAssembly.instantiate(bytes).then(mod=>{
  mod.instance.f(50);
})




WebAssembly 调                JS之前的例子都是用 JS 去调用 WebAssembly 模块,但是在有些场景下可能需要在 WebAssembly 模块中调用浏览器                API,接下来介绍如何在 WebAssembly 中调用 JS。
WebAssembly.instantiate 函数支持第二个参数                WebAssembly.instantiate(bytes,importObject),这个 importObject 参数的作用就是 JS 向                WebAssembly 传入 WebAssembly 中需要调用 JS 的 JS 模块。举个具体的例子,改造前面的计算斐波那契序列在                WebAssembly 中调用 Web 中的 window.alert 函数把计算结果弹出来,为此需要改造加载 WebAssembly 模块的 JS                代码:
1
2
3
4
5
6
7
WebAssembly.instantiate(bytes,{
  window:{
    alert:window.alert
  }
}).then(mod=>{
  mod.instance.f(50);
})




对应的还需要修改 AssemblyScript 编写的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 声明从外部导入的模块类型
declare namespace window {
    export function alert(v: number): void;
}

function _f(x: number): number {
    if (x == 1 || x == 2) {
        return 1;
    }
    return _f(x - 1) + _f(x - 2)
}

export function f(x: number): void {
    // 直接调用 JS 模块
    window.alert(_f(x));
}




修改以上 AssemblyScript 源码后重新用 asc 通过命令 asc f.ts 编译后输出的 wast 文件比之前多了几行:
1
2
3
4
5
6
(import "window" "alert" (func $src/asm/module/window.alert (type 0)))

(func $src/asm/module/f (type 0) (param f64)
    get_local 0
    call $src/asm/module/_f
    call $src/asm/module/window.alert)




多出的这部分 wast 代码就是在 AssemblyScript 中调用 JS 中传入的模块的逻辑。
除了以上常用的 API 外,WebAssembly 还提供一些 API,你可以通过这个 去查看所有 WebAssembly JS API 的细节。
不止于浏览器WebAssembly 作为一种底层字节码,除了能在浏览器中运行外,还能在其它环境运行。
直接执行 wasm                二进制文件前面提到的 Binaryen 提供了在命令行中直接执行 wasm 二进制文件的工具,在 Mac 系统下通过 brew install binaryen                安装成功后,通过 wasm-shell f.wasm 文件即可直接运行。
在 Node.js                中运行目前 V8 JS 引擎已经添加了对 WebAssembly 的支持,Chrome 和 Node.js 都采用了 V8 作为引擎,因此                WebAssembly 也可以运行在 Node.js 环境中;
V8 JS 引擎在运行 WebAssembly 时,WebAssembly 和 JS 是在同一个虚拟机中执行,而不是 WebAssembly                在一个单独的虚拟机中运行,这样方便实现 JS 和 WebAssembly 之间的相互调用。
要让上面的例子在 Node.js 中运行,可以使用以下代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const fs = require('fs');

function toUint8Array(buf) {
    var u = new Uint8Array(buf.length);
    for (var i = 0; i < buf.length; ++i) {
        u = buf;
    }
    return u;
}

function loadWebAssembly(filename, imports) {
    // 读取 wasm 文件,并转换成 byte 数组
    const buffer = toUint8Array(fs.readFileSync(filename));
    // 编译 wasm 字节码到机器码
    return WebAssembly.compile(buffer)
        .then(module => {
            // 实例化模块
            return new WebAssembly.Instance(module, imports)
        })
}

loadWebAssembly('../temp/assembly/module.wasm')
    .then(instance => {
        // 调用 f 函数计算
        console.log(instance.exports.f(10))
    });




在 Nodejs 环境中运行 WebAssembly 的意义其实不大,原因在于 Nodejs 支持运行原生模块,而原生模块的性能比                WebAssembly 要好。 如果你是通过 C、Rust 去编写 WebAssembly,你可以直接编译成 Nodejs                可以调用的原生模块。
WebAssembly                展望从上面的内容可见 WebAssembly 主要是为了解决 JS 的性能瓶颈,也就是说 WebAssembly                适合用于需要大量计算的场景,例如:
  • 在浏览器中处理音视频,  用                    WebAssembly 重写后性能会有很大提升;
  • React 的 dom diff 中涉及到大量计算,用 WebAssembly 重写 React 核心模块能提升性能。Safari                    浏览器使用的 JS 引擎 JavaScriptCore 也已经支持 WebAssembly,RN 应用性能也能提升;
  • 突破大型 3D 网页游戏性能瓶颈, 。
总结WebAssembly 标准虽然已经定稿并且得到主流浏览器的实现,但目前还存在以下问题:
  • 浏览器兼容性不好,只有最新版本的浏览器支持,并且不同的浏览器对 JS WebAssembly 互调的 API 支持不一致;
  • 生态工具不完善不成熟,目前还不能找到一门体验流畅的编写 WebAssembly 的语言,都还处于起步阶段;
  • 学习资料太少,还需要更多的人去探索去踩坑。;
总之现在的 WebAssembly 还不算成熟,如果你的团队没有不可容忍的性能问题,那现在使用 WebAssembly 到产品中还不是时候,                因为这可能会影响到团队的开发效率,或者遇到无法轻易解决的坑而阻塞开发。
返回列表