随笔

Webassembly

WHAT

WebAssembly是一种运行在现代网络浏览器中的新型代码并且提供新的性能特性和效果. -- MDN
截至 2017-11 月, 四大浏览器 preview 版本都已支持 MAP 版本

WHY

参考: JavaScript 发展历程
在之前, 4399, 农场, 偷菜以及在线视频都是基于 Flash 开发
但因为, Flash 非公开, 漏洞多, 移动端不支持等等原因导致 Flash 的死亡

Html5 带来了 Video/Audio, Canvas 以及 WebGL 的支持, 各种层出不同的小游戏, 让大家看到了WEB端的潜力
于是, 大家又瞄上了大型应用在浏览器中实现的可能性

WebAssembly是一门低级的类汇编语言。它有一种紧凑的二进制格式,使其能够以接近原生性能的速度运行并且为诸如C++和Rust等拥有低级的内存模型语言提供了一个编译目标以便它们能够在网络上运行。(注意,WebAssembly有一个在将来支持使用了垃圾回收内存模型的语言的高级目标。)

从历史角度讲,虚拟机过去只能加载JavaScript。这对我们而言足够了,因为JavaScript足够强大从而能够解决人们在当今网络上遇到的绝大部分问题。尽管如此,当试图把JavaScript应用到诸如3D游戏、虚拟现实、增强现实、计算机视觉、图像/视频编辑以及大量的要求原生性能的其他领域的时候,我们遇到了性能问题。

相对于 JS

体积大概小四分之一
省掉解析时间, 一般会占 10% 以上的执行时间, 载入内存后就是 AST 语法树, 不需要经过解析, 解释
底层操作

目标

  • 安全
  • 线程
  • 模块集成 (类似 ES Modules 可以不依赖 JS)
  • 垃圾回收
  • SIMD (单指令多数据流)
    alt

示例

https://webassembly.org/demo/Tanks/
https://websightjs.com/

HOW

Start Coding

https://webassembly.studio/
火狐开发的一个在线工具, 可以直接写代码运行 因为直接写 WebAssembly 非常复杂, 所以我们会选择一种强类型语言来写, 然后转换成 WebAssembly 目前有 C, Rust, AssemblyScript(strictly typed TypeScript)

编译流程

Emscripten -> asm.js -> Binaryen -> Webassembly
前端嘛 看得见的东西才是最终要的
所以, 我们直接使用 Web IDE

示例

// main.ts
export function add(x: i32, y: i32): i32 {
  return x + y;
}
// 编译后的代码
00 61 73 6d 01 00 00 00 01 07 01 60 02 7f 7f 01
7f 03 02 01 00 05 03 01 00 01 06 06 01 7f 00 41
04 0b 07 10 02 03 61 64 64 00 00 06 6d 65 6d 6f
72 79 02 00 0a 0a 01 08 00 20 00 20 01 6a 0f 0b
00 12 04 6e 61 6d 65 01 0b 01 00 08 6d 61 69 6e
2f 61 64 64 00 1f 10 73 6f 75 72 63 65 4d 61 70
70 69 6e 67 55 52 4c 0d 6d 61 69 6e 2e 77 61 73
6d 2e 6d 61 70
// main.js
// 直接执行二进制
WebAssembly.compile(new Uint8Array(`
00 61 73 6d 01 00 00 00 01 07 01 60 02 7f 7f 01
7f 03 02 01 00 05 03 01 00 01 06 06 01 7f 00 41
04 0b 07 10 02 03 61 64 64 00 00 06 6d 65 6d 6f
72 79 02 00 0a 0a 01 08 00 20 00 20 01 6a 0f 0b
00 12 04 6e 61 6d 65 01 0b 01 00 08 6d 61 69 6e
2f 61 64 64 00 1f 10 73 6f 75 72 63 65 4d 61 70
70 69 6e 67 55 52 4c 0d 6d 61 69 6e 2e 77 61 73
6d 2e 6d 61 70`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16))
)).then(module => {
  const instance = new WebAssembly.Instance(module)
  const { add } = instance.exports

  console.log('12 + 21 =', add(12, 21));
});

// 文件下发
WebAssembly.instantiateStreaming(fetch("../out/main.wasm"), {}).
  then(module => {
    const { add } = module.instance.exports;
    console.log('12 + 21 =', add(12, 21));
});

获取字符串

因为默认只支持数字, 所以字符串或者其它类型的都需要操作 memory
如果你写过底层代码或者游戏, 应该比较属性

字符串

我需要拿到整个 memory, 然后拿到变量的 point 和长度, 最后才能拿到想要的数据

// main.ts
// 声明需要JS实现的方法
declare function onGetString(out: string): void;

// 获取字符串
export function getString(): void {
  const str: string = "Hello, I give you a string!";
  onGetString(str);
}
// main.js
let importOptions = {
  env: {
    onGetString: onGetString
  }
};
// 获取字符串回调
function onGetString(index) {
  const length = mem.getUint32(index, true);
  const array = new Uint16Array(mem.buffer, index + 4, length);
  const str = new TextDecoder('utf-16').decode(array);
  console.log(str);
}

WebAssembly.instantiateStreaming(fetch("main.wasm"), {
  env: {
    onGetString: onGetString
  }
}).then(module => {
  const { getString, memory } = module.instance.exports;
  let mem = new DataView(memory.buffer);
  // 开始执行
  getString();
});

FAQ

JavaScript 编译器发展

最开始的时候很简单, 我要动态执行, 有垃圾回收就OK, 所以, 很慢(当然, 我是没体验过那个年代...)
alt

然后, 大约是08年, Chrome 带着 V8 引擎面世了, 并且使用了JIT(Just In Time)技术 alt

Webassembly 处于哪个链路

alt

未来

PS: 如果你现在要去循环跑一段代码去对比测试性能, 你会发现速度其实并没有多大差别

JS 不改革是无法有大幅改进的, 但是, 一个平稳的标准也不不会有人愿意打破它

一个没有历史包袱的东西, 可能有光明的未来, 也可能变成有一个 Dart
目前来说, 内存操作, 多线程, SIMD 并且是一个开放且大家都愿意支持的标准
应该会给浏览器和 JS 带来更加期待的未来

参考

https://hacks.mozilla.org/2017/02/creating-and-working-with-webassembly-modules/ https://zh.wikipedia.org/wiki/%E5%8D%95%E6%8C%87%E4%BB%A4%E6%B5%81%E5%A4%9A%E6%95%B0%E6%8D%AE%E6%B5%81

本文链接:https://note.lilonghe.net/post/webassembly.html

-- EOF --