初探WebAssembly

1.前言

参加完2018年上海的QCon大会,想到了会议中来自Microsoft的朱力旻大佬讲的WebAssembly,感触颇深。
我之前完全没有了解过WebAssembly,之前没有了解的原因也很简单,没有什么实际的应用场景,但工欲善其事,必先利其器。
抱着这样的想法,便开始入坑WebAssembly。

2.Why WebAssembly

2.1 javascript的背景

JavaScript这门由Brendan Eich花了10天创造出来的语言,在如今收到了广泛的应用,同时也因为其缺陷遭受到了业界的一些诟病。的确,这门被仓促的创造出来的语言有很多缺陷。毕竟JavaScript结合了函数式编程和面向对象编程,在JavaScript之前的参考极少。

并且JavaScript发展过于迅速,没有多少时间去调整标准。比如JavaScript的变量是一个动态变量。上一秒这个变量可以是Array,下一秒就变成了一个Number。

JavaScript的语法太过于灵活,这样导致JavaScript在开发大型应用上成为了瓶颈,并且类似于CAD这种网格模型对于性能要求特别高的情况下,JavaScript根本无法胜任。

2.2 社区的补救

例如,微软给出了TypeScript这个解决方案,TypeScript为JavaScript添加了静态类型检查,提升了代码的健壮性。但是最终TypeScript还是要编译成JavaScript来运行。
于是在2013年,为了推动Web性能的发展。WebAssembly的前身asm.js诞生了。下面给个例子。

function asmJS() {
  'use asm';
  
  let myInt = 0 | 0;
  let myDouble = +1.1;
}

传统的JavaScript的动态变量需要在代码执行的时候,编译器才知道当前变量的类型。我们都知道"use strict",这是JavaScript的严格模式。
通过添加代码 "use strict"来使用严格模式。在严格模式下的JavaScript代码会通过抛出错误的方式来代替原来的静默错误。并且在某些情况下, 严格模式下的代码运行效率会高于"sloppy mode"下的代码。
而上述代码中的"use asm",就是告诉引擎,下面的代码是asm.js的代码。当引擎看到"0 | 0",就会在这行代码运行之前知道,这是一个整形的数据,而不会再去编译一次。而看到"+1.1"就会知道这是一个浮点型的数据。asm.js是JavaScript的一个子集。
并且将JavaScript的动态变量变成了静态变量,代码在运行时,有接近原生的性能。

可能到这里疑问就来了,既然asm.js在运行时已经有了接近native的性能。为什么还会出现WebAssembly这个技术。那是asm.js并不能解决所有的问题。

2.3 asm.js并不能解决所有的问题

为什么说asm.js不能解决所有的问题?拿Microsoft Edge的Javascript引擎举个例子。

上面这张图是ChakraCore引擎的结构。Chakra是Microsoft Edge浏览器开源的Chakra Javascript引擎的核心部分。我们在浏览器中运行的Javascript代码首先会经过编译,解析成AST(AKA抽象语法树),想实操的可以去这里试试。
asm.js及时让其运行性能接近了native,但是仍然需要经过parser这一步。而这一步是整个过程中最费时的一步。这就给asm.js造成了一个瓶颈。

2.4 WebAssembly横空出世

于是2015年,WebAssembly出现了。WebAssembly是一个可移植、体积小、加载快并且即兼容Web的汇编格式。

与asm.js类似的是,WebAssembly拥有静态类型。同时也是编译目标,并且可移植。而不同于asm.js,WebAssembly是汇编格式,代码量小,起步相对较快。而且语法上完全脱离JavaScript。这是WebAssembly官方给出的一个demo,可以去体验一下。

3 WebAssembly快速入门

WebAssembly为什么说是编译的目标。是因为WebAssembly可以将C和C++编译成WebAssembly,用的工具是Emscripten,是一个预编译工具。要进行WebAssembly的编译,需要安装git、cmake、python
假设你是在mac上进行操作的。

3.1 安装

安装WebAssembly工具链。

git clone https://github.com/juj/emsdk.git
cd emsdk
./emsdk install --build=Release sdk-incoming-64bit binaryen-master-64bit
./emsdk activate --build=Release sdk-incoming-64bit binaryen-master-64bit

可能会等的有点久,并且可能会出现cmake没有安装的报错信息。可以使用homebrew安装cmake后,再执行安装操作。

3.2 设置环境变量

在emsdk目录下,运行下列命令。就可以在当前的终端窗口中运行emcc命令了。

source ./emsdk_env.sh --build=Release

3.3 编写C或者C++文件

新建一个C文件,代码如下。

#include <stdio.h>

long factorial(int num) {
    if (num <= 0) return 1;
    else {
        return num * factorial(num - 1);
    } 
}


int main () {
    int num = factorial(10);
    printf("The Result: %d 
", num);
}

3.4 利用Emscripten编译代码

然后在相同目录下执行emcc的编译命令,该命令会将C或者C++文件编译成wasm模块。

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

其中,hello.c为源文件,-o hello.html是这个命令的输出文件,-s WASM指定输出的格式为wasm,并且版本为1。执行这个文件后,会在目录下生成如下数据。

.
├── hello.c
├── hello.html
├── hello.js
└── hello.wasm

hello.wasm就是经过编译后的C代码的二进制文件。而hello.js则是C语言与JavaScript相互转化的中间代码。执行下面的代码就可以在localhost的8080端口看到我们的C模块在前端的调用和展示。

emrun --no_browser --port 8080 .

然后就可以在浏览器中看到如下的输出。

我们编写的C模块已经可以在浏览器中正常的运行了。这说明我们的WebAssembly编译成功了。

4 在前端项目中使用Wasm

可以参考这篇文章,如何在React项目中直接使用WebAssembly,在这篇文章的项目中可以看到完整的从wasm文件中提取出函数的function,以及清晰的用C实现的函数和用JavaScript实现的函数的运行性能对比。

参考资料

原文地址:https://www.cnblogs.com/detectiveHLH/p/9881640.html