俄罗斯贵宾会-俄罗斯贵宾会官网
做最好的网站

悄悄掀起 WebAssembly 的神秘面纱

一个 WebAssembly 与 JavaScript 数据互通交互的例子

在 WebAssembly 中有一块内存,这块内存可以是内部定义的,也可以是从外面导入的,如果是内部定义的,则可以通过 export 进行导出。JavaScript 在拿到这块“内存”后,是拥有完全操作的权利的。JavaScript 使用 DataViewMemory 对象进行包装后,就可以使用 DataView 下面的函数对内存对象进行读取或写入操作。

这里是一个简单的例子:

;; example.wat (module (import "env" "mem" (memory 1)) (import "js" "log" (func $log (param i32))) (func (export "example") i32.const 0 i64.const 8022916924116329800 i64.store (i32.store (i32.const 8) (i32.const 560229490)) (call $log (i32.const 0))))

1
2
3
4
5
6
7
8
9
10
;; example.wat
(module
  (import "env" "mem" (memory 1))
  (import "js" "log" (func $log (param i32)))
  (func (export "example")
    i32.const 0
    i64.const 8022916924116329800
    i64.store
    (i32.store (i32.const 8) (i32.const 560229490))
    (call $log (i32.const 0))))

这个代码首先从 env.mem 导入一个内存对象作为默认内存,这和前面的例子是一样的。

然后从 js.log 导入一个函数,这个函数拥有一个 32 位整型的参数,不需要返回值,在 wat 内部被命名为“$log”,这个名字只存在于 wat 文件中,在编译为 wasm 后就不存在了,只存储一个偏移地址。

后面定义了一个函数,并导出为“example”函数。在 WebAssembly 中,函数里的内容都是在栈上的。

首先,使用 i32.const 0 在栈内压入一个 32 位整型常数 0,然后使用 i64.const 8022916924116329800 在栈内压入一个 64 位整型常数 8022916924116329800,之后调用 i64.store 指令,这个指令将会将栈顶部第一个位置的一个 64 位整数存储到栈顶部第二个位置指定的“内存地址”开始的连续 8 个字节空间中。

TL; DR; 简而言之,就是在内存的第 0 个位置开始的连续 8 个字节的空间里,存入一个 64 位整型数字 8022916924116329800。这个数字转为 16 进制表示为:0x 6f 57 20 6f 6c 6c 65 48,但是由于 WebAssembly 中规定字节序是使用“小端序(Little-Endian Byte Order)”来存储数据,所以,在内存中第 0 个位置存储的是 0x48,第 1 个位置存储的是 0x65……所以,最终存储的实际上是 0x 48 65 6c 6c 6f 20 57 6f,对应着 ASCII 码为:“Hello Wo”。

然后,后面的一句指令 (i32.store (i32.const 8) (i32.const 560229490)) 的格式是上面三条指令的“S-表达式”形式,只不过这里换成了 i32.store 来存储一个 32 位整型常数 560229490 到 8 号“内存地址”开始的连续 4 个字节空间中。

实际上这一句指令的写法写成上面三句的语法是完全等效的:

i32.const 8 i32.const 560229490 i32.store

1
2
3
i32.const 8
i32.const 560229490
i32.store

类似的,这里是在内存的第 8 个位置开始的连续 4 个字节的空间里,存入一个 32 位整型数字 560229490。这个数字转为 16 进制表示位:0x 21 64 6c 72,同样采用“小端序”来存储,所以存储的实际上是 0x 72 6c 64 21,对应着 ASCII 码为:“rld!“。

所以,最终,内存中前 12 个字节中的数据为 0x 48 65 6c 6c 6f 20 57 6f 72 6c 64 21,连起来就是对应着 ASCII 码:“Hello World!“。

将这个 wat 编译为 wasm 后,文件大小为 95 Bytes:

$ wat2wasm example.wat $ xxd example.wasm 00000000: 0061 736d 0100 0000 0108 0260 017f 0060 .asm.......`...` 00000010: 0000 0215 0203 656e 7603 6d65 6d02 0001 ......env.mem... 00000020: 026a 7303 6c6f 6700 0003 0201 0107 0b01 .js.log......... 00000030: 0765 7861 6d70 6c65 0001 0a23 0121 0041 .example...#.!.A 00000040: 0042 c8ca b1e3 f68d c8ab ef00 3703 0041 .B..........7..A 00000050: 0841 f2d8 918b 0236 0200 4100 1000 0b .A.....6..A....

1
2
3
4
5
6
7
8
$ wat2wasm example.wat
$ xxd example.wasm
00000000: 0061 736d 0100 0000 0108 0260 017f 0060  .asm.......`...`
00000010: 0000 0215 0203 656e 7603 6d65 6d02 0001  ......env.mem...
00000020: 026a 7303 6c6f 6700 0003 0201 0107 0b01  .js.log.........
00000030: 0765 7861 6d70 6c65 0001 0a23 0121 0041  .example...#.!.A
00000040: 0042 c8ca b1e3 f68d c8ab ef00 3703 0041  .B..........7..A
00000050: 0841 f2d8 918b 0236 0200 4100 1000 0b    .A.....6..A....

接下来,还是使用 JavaScript 编写“胶水代码”:

JavaScript

// example.js const file = await fetch('./example.wasm'); const memory = new window.WebAssembly.Memory({ initial: 1 }); const dv = new DataView(memory); const log = offset => { let length = 0; let end = offset; while(end < dv.byteLength && dv.getUint8(end) > 0) { ++length; ++end; } if (length === 0) { console.log(''); return; } const buf = new ArrayBuffer(length); const bufDv = new DataView(buf); for (let i = 0, p = offset; p < end; ++i, ++p) { bufDv.setUint8(i, dv.getUint8(p)); } const result = new TextDecoder('utf-8').decode(buf); console.log(result); }; const mod = await window.WebAssembly.instantiateStreaming(file, { env: { mem: memory, }, js: { log }, }); mod.instance.exports.example(); // 调用 WebAssembly 模块导出的 example 函数

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
30
31
// example.js
 
const file = await fetch('./example.wasm');
const memory = new window.WebAssembly.Memory({ initial: 1 });
const dv = new DataView(memory);
const log = offset => {
  let length = 0;
  let end = offset;
  while(end < dv.byteLength && dv.getUint8(end) > 0) {
    ++length;
    ++end;
  }
  if (length === 0) {
    console.log('');
    return;
  }
  const buf = new ArrayBuffer(length);
  const bufDv = new DataView(buf);
  for (let i = 0, p = offset; p < end; ++i, ++p) {
    bufDv.setUint8(i, dv.getUint8(p));
  }
  const result = new TextDecoder('utf-8').decode(buf);
  console.log(result);
};
const mod = await window.WebAssembly.instantiateStreaming(file, {
  env: {
    mem: memory,
  },
  js: { log },
});
mod.instance.exports.example();  // 调用 WebAssembly 模块导出的 example 函数

这里,使用 DataViewmemory 进行了一次包装,这样就可以方便地对内存对象进行读写操作了。

然后,这里在 JavaScript 中实现了一个 log 函数,函数接受一个参数(这个参数在上面的 wat 中指定了是整数型)。下面的实现首先是确定输出的字符串长度(字符串通常以 '' 结尾),然后将字符串复制到一个长度合适的 ArrayBuffer 中,然后使用浏览器中的 TextDecoder 类对其进行字符串解码,就得到了原始字符串。

最后,将 log 函数放入 importObject 的 js.log 中,实例化 WebAssembly 模块,最后调用导出的 example 函数,就可以看到打印的 Hello World

图片 1

通过 WebAssembly,我们可以将很多其他语言编写的类库直接封装到浏览器中运行,比如 Google Developers 就给了一个使用 WebAssembly 加载一个使用 C 语言编写的 WebP 图片编码库,将一张 jpg 格式的图片转换为 webp 格式并显示出来的例子:https://developers.google.com/web/updates/2018/03/emscripting-a-c-library

这个例子使用 Emscripten 工具对 C 语言代码进行编译,这个工具在安装的时候需要到 GitHub、亚马逊 S3 等服务器下载文件,在国内这神奇的网络环境下速度异常缓慢,总共几十兆的文件可能挂机一天都下不完。可以尝试修改 emsdk 文件(Python),增加代理配置(但是效果不明显),或是在下载的过程中会提示下载链接和存放路径,使用其他工具下载后放到指定地方,重新安装会自动跳过已经下载的文件。

编写加载函数(loader)

function loadWebAssembly (path) {

  return fetch(path)                   // 加载文件        

    .then(res => res.arrayBuffer())    // 转成 ArrayBuffer

    .then(WebAssembly.instantiate)     // 编译 + 实例化

    .then(mod => mod.instance)         // 提取生成都模块

}

完成了上边的操作,就可以直接使用 loadWebAssembly 这个方法加载 wasm 文件了,它相当于是一个 wasm-loader ;返回值是一个 Promise.

loadWebAssembly('path/to/math.wasm')

  .then(instance => {

    const { add, square } = instance.exports

    // ...

})

更完善的loader

function loadWebAssembly(filename, imports = {}) {

return fetch(filename)

    .then(response => response.arrayBuffer())

    .then(buffer => WebAssembly.compile(buffer)) 

    //WebAssembly.compile 可以用来编译 wasm 的二进制源码,

    //它接受 BufferSource 格式的参数,返回一个 Promise。

    .then(module => {           

        imports.env = imports.env || {};

        // 开辟内存空间 && 创建变量映射表

        Object.assign(imports.env, {

            memoryBase: 0,

            tableBase: 0,

            memory: new WebAssembly.Memory({ initial: 256, maximum: 256 }),

            table: new WebAssembly.Table({ initial: 0, maximum: 0, 

                    element: 'anyfunc' })

        })

        // 创建 WebAssembly 实例

        return new WebAssembly.instantiate(module, imports)

    })

}

ArrayBuffer 做了两件事情,一件是做 WebAssembly 的内存,另外一件是做 JavaScript 的对象。

它使 JS 和 WebAssembly 之间传递内容更方便。 使内存管理更安全。

这个 loadWebAssembly 函数还接受第二个参数,表示要传递给 wasm 的变量,在初始化 WebAssembly 实例的时候,可以把一些接口传递给 wasm 代码。

一个非常简单的例子

我们来看一个非常简单的例子,这个已经在 Chrome 69 Canary 和 Chrome 70 Canary 中测试通过,理论上可以在所有已经支持 WebAssembly 的浏览器中运行。(在后文中有浏览器的支持情况)

首先,我们先使用 S-表达式 编写一个十分简单的程序:

;; test.wat (module (import "env" "mem" (memory 1)) ;; 这里指定了从 env.mem 中导入一个内存对象 (func (export "get") (result i32) ;; 定义并导出一个叫做“get”的函数,这个函数拥有一个 int32 类型的返回值,没有参数 memory.size)) ;; 最终返回 memory 对象的“尺寸”(单位为“页”,目前规定 1 页 = 64 KiB = 65536 Bytes)

1
2
3
4
5
;; test.wat
(module
  (import "env" "mem" (memory 1)) ;; 这里指定了从 env.mem 中导入一个内存对象
  (func (export "get") (result i32)  ;; 定义并导出一个叫做“get”的函数,这个函数拥有一个 int32 类型的返回值,没有参数
    memory.size))  ;; 最终返回 memory 对象的“尺寸”(单位为“页”,目前规定 1 页 = 64 KiB = 65536 Bytes)

可以使用 wabt 中的 wasm2wat 工具将 wasm 文件转为使用“S-表达式”进行描述的 wat 文件。同时也可以使用 wat2wasm 工具将 wat 转为 wasm。

在 wat 文件中,双分号 ;; 开头的内容都是注释。

上面这个 wat 文件定义了一个 module,并导入了一个内存对象,然后导出了一个叫做“get”的函数,这个函数返回当前内存的“尺寸”。

在 WebAssembly 中,线性内存可以在内部直接定义然后导出,也可以从外面导入,但是最多只能拥有一个内存。这个内存的大小并不是固定的,只需要给一个初始大小 initial,后期还可以根据需要调用 grow 函数进行扩展,也可以指定最大大小 maximum(这里所有内存大小的单位都是“页”,目前规定的是 1 页 = 64 KiB = 65536 Bytes。)

上面这个 wat 文件使用 wat2wasm 编译为 wasm 后生成的文件体积非常小,只有 50 Bytes:

$ wat2wasm test.wat $ xxd test.wasm 00000000: 0061 736d 0100 0000 0105 0160 0001 7f02 .asm.......`.... 00000010: 0c01 0365 6e76 036d 656d 0200 0103 0201 ...env.mem...... 00000020: 0007 0701 0367 6574 0000 0a06 0104 003f .....get.......? 00000030: 000b ..

1
2
3
4
5
6
$ wat2wasm test.wat
$ xxd test.wasm
00000000: 0061 736d 0100 0000 0105 0160 0001 7f02  .asm.......`....
00000010: 0c01 0365 6e76 036d 656d 0200 0103 0201  ...env.mem......
00000020: 0007 0701 0367 6574 0000 0a06 0104 003f  .....get.......?
00000030: 000b                                     ..

为了让这个程序能在浏览器中运行,我们还必须使用 JavaScript 编写一段“胶水代码(glue code)”,以便这个程序能被加载到浏览器中并执行:

// main.js const file = await fetch('./test.wasm'); const memory = new window.WebAssembly.Memory({ initial: 1 }); const mod = await window.WebAssembly.instantiateStreaming(file, { env: { mem: memory, }, }); let result; result = mod.instance.exports.get(); // 调用 WebAssembly 模块导出的 get 函数 console.log(result); // 1 memory.grow(2); result = mod.instance.exports.get(); // 调用 WebAssembly 模块导出的 get 函数 console.log(result); // 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// main.js
 
const file = await fetch('./test.wasm');
const memory = new window.WebAssembly.Memory({ initial: 1 });
const mod = await window.WebAssembly.instantiateStreaming(file, {
  env: {
    mem: memory,
  },
});
let result;
result = mod.instance.exports.get();  // 调用 WebAssembly 模块导出的 get 函数
console.log(result);  // 1
memory.grow(2);
result = mod.instance.exports.get();  // 调用 WebAssembly 模块导出的 get 函数
console.log(result);  // 3
 

这里我使用了现代浏览器都已经支持的 ES6 语法,首先,使用浏览器原生提供的 fetch 函数加载我们编译好的 test.wasm 文件。注意,这里根据规范,HTTP 响应的 Content-Type 中指定的 MIME 类型必须为 application/wasm

接下来,我们 new 了一个 WebAssembly.Memory 对象,通过这个对象,可以实现 JavaScript 与 WebAssembly 之间互通数据。

再接下来,我们使用了 WebAssembly.instantiateStreaming 来实例化加载的 WebAssembly 模块,这里第一个参数是一个 Readable Stream,第二个参数是 importObject,用于指定导入 WebAssembly 的结构。因为上面的 wat 代码中指定了要从 env.mem 导入一个内存对象,所以这里就得要将我们 new 出来的内存对象放到 env.mem 中。

WebAssembly 还提供了一个 instantiate 函数,这个函数的第一个参数可以提供一个 ArrayBuffer 或是 TypedArray。但是这个函数是不推荐使用的,具体原因做过流量代理转发的同学可能会比较清楚,这里就不具体解释了。

最后,我们就可以调用 WebAssembly 导出的函数 get 了,首先输出的内容为 memoryinitial 的值。然后我们调用了 memory.grow 方法来增长 memory 的尺寸,最后输出的内容就是增长后内存的大小 1 + 2 = 3

简介

JS于1995年问世,设计的初衷不是为了执行起来快。直到08年性能大战中,许多浏览器引入了即时编译 JIT(just-in-time编译器),JavaScript 代码的运行渐渐变快。正是由于这些 JIT 的引入,使得 JavaScript 的性能达到了一个转折点,JS 代码执行速度快了 20 – 50倍。

JIT 是使 JavaScript 运行更快的一种手段,通过监视代码的运行状态,把 hot 代码(重复执行多次的代码)进行优化。通过这种方式,可以使 JavaScript 应用的性能提升很多倍。

图片 2

随着性能的提升,JavaScript 可以应用到以前根本没有想到过的领域,比如用于后端开发的 Node.js。性能的提升使得 JavaScript 的应用范围得到很大的扩展。

JavaScript的无类型是JavaScript引擎的性能瓶颈之一,在过去几年,我们看到越来越多的项目问世,它们试图通过开发编译程序,将其他语言代码转化为 JavaScript,以此让开发者克服 JavaScript 自身存在的一些短板。其中一些项目专注于给编程语言增加新的功能,比如微软的 TypeScript 和 Google 的 Dart,【设计一门新的强类型语言并强制开发者进行类型指定】或是加快 JavaScript 的执行速度,例如 Mozilla 的 asm.js 项目和Google的PNaCI【给现有的JavaScript加上变量类型】。

现在通过 WebAssembly,我们很有可能正处于第二个拐点。

图片 3

什么是webAssembly?

WebAssembly是一种新的适合于编译到Web的,可移植的,大小和加载时间高效的格式,是一种新的字节码格式。它的缩写是”.wasm”,.wasm 为文件名后缀,是一种新的底层安全的“二进制”语法。它被定义为“精简、加载时间短的格式和执行模型”,并且被设计为Web 多编程语言目标文件格式。

这意味着浏览器端的性能会得到极大提升,它也使得我们能够实现一个底层构建模块的集合.

webAssembly的优势

webassembly相较于asm.js的优势主要是涉及到性能方面。根据WebAssembly FAQ的描述:在移动设备上,对于很大的代码库,asm.js仅仅解析就需要花费20-40秒,而实验显示WebAssembly的加载速度比asm.js快了20倍,这主要是因为相比解析 asm.js 代码,JavaScript 引擎破译二进制格式的速度要快得多。

主流的浏览器目前均支持webAssembly。

Safari 支持 WebAssembly的第一个版本是11 Edge 支持 WebAssembly的第一个版本是16 Firefox 支持 WebAssembly的第一个版本是 52 chrome 支持 WebAssembly的第一个版本是 57

使用WebAssembly,我们可以在浏览器中运行一些高性能、低级别的编程语言,可用它将大型的C和C++代码库比如游戏、物理引擎甚至是桌面应用程序导入Web平台。

WebAssembly

为了能让代码跑得更快,WebAssembly 出现了(并且现在主流浏览器也都开始支持了),它能够允许你预先使用“编译”的方法将代码编译好后,直接放在浏览器中运行,这一步就做得比较彻底了,不再需要 JIT 来动态得进行优化了,所有优化都可以在编译的时候直接确定。

WebAssembly 到底是什么呢?

首先,它不是直接的机器语言,因为世界上的机器太多了,它们都说着不同的语言(架构不同),所以很多情况下都是为各种不同的机器架构专门生成对应的机器代码。但是要为各种机器都生成的话,太复杂了,每种语言都要为每种架构编写一个编译器。为了简化这个过程,就有了“中间代码(Intermediate representation,IR)”,只要将所有代码都翻译成 IR,再由 IR 来统一应对各种机器架构。

实际上,WebAssembly 和 IR 差不多,就是用于充当各种机器架构翻译官的角色。WebAssembly 并不是直接的物理机器语言,而是抽象出来的一种虚拟的机器语言。从 WebAssembly 到机器语言虽说也需要一个“翻译”过程,但是在这里的“翻译”就没有太多的套路了,属于机器语言到机器语言的翻译,所以速度上已经非常接近纯机器语言了。

这里有一个 WebAssembly 官网上提供的 Demo,是使用 Unity 开发并发布为 WebAssembly 的一个小游戏:https://webassembly.org/demo/,可以去体验体验。

webAssembly.Module

WebAssembly.Module() 构造函数可以用来同步编译给定的 WebAssembly 二进制代码。不过,获取 Module 对象的主要方法是通过异步编译函数,如 WebAssembly.compile(),或者是通过 IndexedDB 读取 Module 对象.

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

module: 需要被实例化的webAssembly module importObject: 需要导入的变量

.wasm 文件 与 .wat 文件

WebAssembly 是通过 *.wasm 文件进行存储的,这是编译好的二进制文件,它的体积非常的小。

在浏览器中,提供了一个全局的 window.WebAssembly 对象,可以用于实例化 WASM 模块。

图片 4

WebAssembly 是一种“虚拟机器语言”,所以它也有对应的“汇编语言”版本,也就是 *.wat 文件,这是 WebAssembly 模块的文本表示方法,采用“S-表达式(S-Expressions)”进行描述,可以直接通过工具将 *.wat 文件编译为 *.wasm 文件。熟悉 LISP 的同学可能对这种表达式语法比较熟悉。

webAssembly.instantiate

Promise WebAssembly.instantiate(module, importObject);

悄悄掀起 WebAssembly 的神秘面纱

2018/09/05 · JavaScript · webassembly

原文出处: WebAssembly   

图片 5

前端开发人员想必对现代浏览器都已经非常熟悉了吧?HTML5,CSS4,JavaScript ES6,这些已经在现代浏览器中慢慢普及的技术为前端开发带来了极大的便利。

得益于 JIT(Just-in-time)技术,JavaScript 的运行速度比原来快了 10 倍,这也是 JavaScript 被运用得越来越广泛的原因之一。但是,这是极限了吗?

随着浏览器技术的发展,Web 游戏眼看着又要“卷土重来”了,不过这一次不是基于 Flash 的游戏,而是充分利用了现代 HTML5 技术实现。JavaScript 成为了 Web 游戏的开发语言,但是对于游戏这样需要大量运算的程序来说,即便是有 JIT 加持,JavaScript 的性能还是不能满足人类贪婪的欲望。

未来功能

直接操作DOM 支持多数据(SIMD):SIMD的使用可以获取大的数据结构,例如不同数目的向量,并且同时将相同的指令应用于不同的部分。这样,它可以大大加快各种复杂计算的游戏或VR的运行速度。 ES6模块集成:浏览器目前正在添加对使用script标签加载JavaScript模块的支持。 添加此功能后,即使URL指向WebAssembly模块, <

WebAssembly 的现状与未来

目前 WebAssembly 的二进制格式版本已经确定,未来的改进也都将以兼容的形式进行更新,这表示 WebAssembly 已经进入现代标准了。

图片 6

现在的 WebAssembly 还并不完美,虽说已经有使用 WebAssembly 开发的 Web 游戏出现了,但是还有很多不完美的地方。

比如,现在的 WebAssembly 还必须配合“JavaScript glue code”来使用,也就是必须使用 JavaScript 来 fetch WebAssembly 的文件,然后调用 window.WebAssembly.instantiatewindow.WebAssembly.instantiateStreaming 等函数进行实例化。部分情况下还需要 JavaScript 来管理堆栈。官方推荐的编译工具 Emscripten 虽然使用了各种黑科技来缩小编译后生成的代码的数量,但是最终生成的 JavaScript Glue Code 文件还是至少有 15K。

未来,WebAssembly 将可能直接通过 HTML 标签进行引用,比如:<script src="./wa.wasm"></script>;或者可以通过 JavaScript ES6 模块的方式引用,比如:import xxx from './wa.wasm';

线程的支持,异常处理,垃圾收集,尾调用优化等,都已经加入 WebAssembly 的计划列表中了。

webAssembly使用

WebAssembly 与其他的汇编语言不一样,它不依赖于具体的物理机器。可以抽象地理解成它是概念机器的机器语言,而不是实际的物理机器的机器语言。浏览器把 WebAssembly 下载下来后,可以迅速地将其转换成机器汇编代码。

图片 7

快速体验webAssembly

WebAssembly.compile(new Uint8Array(`

  00 61 73 6d   01 00 00 00   01 0c 02 60   02 7f 7f 01

  7f 60 01 7f   01 7f 03 03   02 00 01 07   10 02 03 61

  64 64 00 00   06 73 71 75   61 72 65 00   01 0a 13 02

  08 00 20 00   20 01 6a 0f   0b 08 00 20   00 20 00 6c

  0f 0b`.trim().split(/[srn]+/g).map(str => parseInt(str, 16))

)).then(module => {

  const instance = new WebAssembly.Instance(module)

//使用 WebAssembly.Instance 将模块对象转成 WebAssembly 实例

  const { add, square } = instance.exports

//通过 instance.exports 可以拿到 wasm 代码输出的接口

  console.log('2 + 4 =', add(2, 4))

  console.log('3^2 =', square(3))

  console.log('(2 + 5)^2 =', square(add(2 + 5)))

})

使用C/C++

hello.c

#include 

int main(int argc, char ** argv) {

  printf("Hello Worldn");

  return 0;

}

编译:

emcc hello.c -s WASM=1 -o hello.html

-s WASM=1 — 指定我们想要的wasm输出形式。如果我们不指定这个选项,Emscripten默认将只会生成asm.js。

-o hello.html — 指定这个选项将会生成HTML页面来运行我们的代码,并且会生成wasm模块以及编译和实例化wasim模块所需要的“胶水”js代码,这样我们就可以直接在web环境中使用了。

编译后

图片 8

二进制的wasm模块代码 (hello.wasm)

一个包含了用来在原生C函数和JavaScript/wasm之间转换的胶水代码的JavaScript文件 (hello.js)

一个用来加载,编译,实例化你的wasm代码并且将它输出在浏览器显示上的一个HTML文件 (hello.html)

调用C++中的方法

hello.c

#include 



int main(int argc, char ** argv) {

  printf("Hello Worldn");

}

#ifdef __cplusplus

extern "C" {

#endif

int EMSCRIPTEN_KEEPALIVE myFunction(int argc, char ** argv) {

  printf("MyFunction Calledn");

}

#ifdef __cplusplus

}

#endif

如果想调用hello2.c中的myFunction方法,则需要将ccall方法从Moudule导出。使用下面的编译命令:

 emcc -o hello2.html hello2.c -O3 -s 

 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall"]'  

-s WASM=1 --shell-file html_template/shell_minimal.html

html_template/shell_minimal.html 指定为HTML模板。 -s
‘EXTRA_EXPORTED_RUNTIME_METHODS=[“ccall”]’ 从Module中导出 ccall

将 ccall 方法导出之后,就可以使用 Module.ccall来调用C++中的函数了。

var result = Module.ccall(

    'funcName',     // 函数名

    'number',        // 返回类型

    ['number'],      // 参数类型

    [42]);            // 参数

JavaScript 在浏览器中是怎么跑起来的?

对于现在的计算机来说,它们只能读懂“机器语言”,而人类的大脑能力有限,直接编写机器语言难度有点大,为了能让人更方便地编写程序,人类发明了大量的“高级编程语言”,JavaScript 就属于其中特殊的一种。

为什么说是特殊的一种呢?由于计算机并不认识“高级编程语言”写出来的东西,所以大部分“高级编程语言”在写好以后都需要经过一个叫做“编译”的过程,将“高级编程语言”翻译成“机器语言”,然后交给计算机来运行。但是,JavaScript 不一样,它没有“编译”的过程,那么机器是怎么认识这种语言的呢?

实际上,JavaScript 与其他一部分脚本语言采用的是一种“边解释边运行”的姿势来运行的,将代码一点一点地翻译给计算机。

那么,JavaScript 的“解释”与其他语言的“编译”有什么区别呢?不都是翻译成“机器语言”吗?简单来讲,“编译”类似于“全文翻译”,就是代码编写好后,一次性将所有代码全部编译成“机器语言”,然后直接交给计算机;而“解释”则类似于“实时翻译”,代码写好后不会翻译,运行到哪,翻译到哪。

“解释”和“编译”两种方法各有利弊。使用“解释”的方法,程序编写好后就可以直接运行了,而使用“编译”的方法,则需要先花费一段时间等待整个代码编译完成后才可以执行。这样一看似乎是“解释”的方法更快,但是如果一段代码要执行多次,使用“解释”的方法,程序每次运行时都需要重新“解释”一遍,而“编译”的方法则不需要了。这样一看,“编译”的整体效率似乎更高,因为它永远只翻译一次,而“解释”是运行一次翻译一次。并且,“编译”由于是一开始就对整个代码进行的,所以可以对代码进行针对性的优化。

JavaScript 是使用“解释”的方案来运行的,这就造成了它的效率低下,因为代码每运行一次都要翻译一次,如果一个函数被循环调用了 10 次、100 次,这个执行效率可想而知。

好在聪明的人类发明了 JIT(Just-in-time)技术,它综合了“解释”与“编译”的优点,它的原理实际上就是在“解释”运行的同时进行跟踪,如果某一段代码执行了多次,就会对这一段代码进行编译优化,这样,如果后续再运行到这一段代码,则不用再解释了。

JIT 似乎是一个好东西,但是,对于 JavaScript 这种动态数据类型的语言来说,要实现一个完美的 JIT 非常难。为什么呢?因为 JavaScript 中的很多东西都是在运行的时候才能确定的。比如我写了一行代码:const sum = (a, b, c) => a + b + c;,这是一个使用 ES6 语法编写的 JavaScript 箭头函数,可以直接放在浏览器的控制台下运行,这将声明一个叫做 sum 的函数。然后我们可以直接调用它,比如:console.log(sum(1, 2, 3)),任何一个合格的前端开发人员都能很快得口算出答案,这将输出一个数字 6。但是,如果我们这样调用呢:console.log(sum('1', 2, 3)),第一个参数变成了一个字符串,这在 JavaScript 中是完全允许的,但是这时得到的结果就完全不同了,这会导致一个字符串和两个数字进行连接,得到 "123"。这样一来,针对这一个函数的优化就变得非常困难了。

虽说 JavaScript 自身的“特性”为 JIT 的实现带来了一些困难,但是不得不说 JIT 还是为 JavaScript 带来了非常可观的性能提升。

为什么WebAssembly更快

JS 引擎在图中各个部分所花的时间取决于页面所用的 JavaScript 代码。图表中的比例并不代表真实情况下的确切比例情况。

图片 9

图片 10

Parse: 把源代码变成解释器可以运行的代码所花的时间; Compiling + optimizing: 基线编译器和优化编译器花的时间; Re-optimize: 当 JIT 发现优化假设错误,丢弃优化代码所花的时间。 Execut:执行代码的时间 Garbage collection: 垃圾回收,清理内存的时间

文件获取:

WebAssembly比JS的压缩了更高,所以文件获取更快。

解析:

到达浏览器时,JS源代码被解析成了抽象语法树,浏览器采用懒加载的方式进行,只解析真正需要的部分,,而对于浏览器暂时不需要的函数只保留它的桩,解析过后 AST (抽象语法树)就变成了中间代码(叫做字节码),提供给 JS 引擎编译。

而WebAssembly不需要这种转换,因为它本身就是中间代码,它要做的只是解码并且检查确认代码没有错误即可。

图片 11

编译和优化

JavaScript 是在代码的执行阶段编译的。因为它是弱类型语言,当变量类型发生变化时,同样的代码会被编译成不同版本。

不同浏览器处理 WebAssembly 的编译过程也不同。不论哪种方式,WebAssembly 都更贴近机器码,所以它更快.

在编译优化代码之前,它不需要提前运行代码以知道变量都是什么类型。 编译器不需要对同样的代码做不同版本的编译。 很多优化在 LLVM 阶段就已经做完了,所以在编译和优化的时候没有太多的优化需要做。

图片 12

重优化

JS的代码由于类型的不确定性,有些情况下,JIT会返回进行 “抛弃优化代码<->重优化”过程。

而WebAssembly中,类型都是确定了的,因为没有重优化阶段。

执行

WebAssembly 就是为了编译器而设计的,开发人员不直接对其进行编程,这样就使得 WebAssembly 专注于提供更加理想的指令给机器。

执行效率方面,不同的代码功能有不同的效果,一般来讲执行效率会提高 10% - 800%。

图片 13

垃圾回收

WebAssembly不支持垃圾回收,内存操作需要手动控制,因此WebAssembly没有垃圾回收。

小结

WebAssembly 的出现,使得前端不再只能使用 JavaScript 进行开发了,C、C++、Go 等等都可以为浏览器前端贡献代码。

这里我使用 wat 文件来编写的两个例子仅供参考,实际上在生产环境不大可能直接使用 wat 来进行开发,而是会使用 C、C++、Go 等语言编写模块,然后发布为 WebAssembly。

WebAssembly 的出现不是要取代 JavaScript,而是与 JavaScript 相辅相成,为前端开发带来一种新的选择。将计算密集型的部分交给 WebAssembly 来处理,让浏览器发挥出最大的性能!

1 赞 收藏 评论

图片 14

webAssembly的方法

本文由俄罗斯贵宾会发布于Web前端,转载请注明出处:悄悄掀起 WebAssembly 的神秘面纱

您可能还会对下面的文章感兴趣: