Hi, WebAssembly

Hi, WebAssembly

说到前端,大家脑海中的飞过的第一个概念肯定是 Javascript + CSS + HTML,从 1995 诞生之初到现在,可以说是长盛不衰,一直位居最火编程语言之一的宝座,也是从那个时候到如今这么多年的发展,前端的语言基础基本确立,就是上面说的三剑客。很多浏览器加入了即时编译器,又称之为 JITs。在这种模式下,JavaScript 在运行的时候,JIT 选择模式然后基于这些模式使代码运行更快。这些 JITs 的引入是浏览器运行代码机制的一个转折点。所有的突然之间,JavaScript 的运行速度快了 10 倍。

发展

随着这种改进的性能,JavaScript 开始被用于意想不到的事情,比如依靠强大的 NodeJS 平台, JS 开始像病毒一样蔓延各个领域, WebService,WebAPI,CI/CD,结合 Electron 模块, 甚至在桌面开发都占有了一丝地位…..,虽然 JS 过于强大,但是随着 Web 应用的需求和野性日益膨胀,JS 也开始暴漏了诸多问题,暂时不说混乱的隐式类型转换之类的问题,最大的问题在于性能瓶颈,前端的开发逻辑越来越复杂,相应的代码量随之变的越来越多。相应的,整个项目的起步的时间越来越长。在性能不好的电脑上,启动一个前端的项目甚至要花上十多秒。

除了逻辑复杂、代码量大,还有另一个原因是 JavaScript 这门语言本身的缺陷,JavaScript 没有静态变量类型。这门解释型编程语言的作者 Brendan Eich,仓促的创造了这门如果被广泛使用的语言,以至于 JavaScript 的发展史甚至在某种层面上变成了填坑史。为什么说没有静态类型会降低效率。这会涉及到一些 JavaScript 引擎的一些知识。

JavaScript 执行过程

我们来看一看 JavaScript 代码在引擎中会经历什么。

  • 1.JavaScript 文件被下载到客户端。
  • 2.进入 Parser,Parser 会把代码转化成 AST(抽象语法树).
  • 3.然后根据抽象语法树,Bytecode Compiler 字节码编译器会生成引擎能够直接阅读、执行的字节码。
  • 4.字节码进入翻译器,将字节码一行一行的翻译成效率十分高的 Machine Code.

在项目运行的过程中,引擎会对执行次数较多的 function 记性优化,引擎将其代码编译成 Machine Code 后打包送到顶部的 Just-In-Time(JIT) Compiler,下次再执行这个 function,就会直接执行编译好的 Machine Code。但是由于 JavaScript 的动态变量,上一秒可能是 Array,下一秒就变成了 Object。那么上一次引擎所做的优化,就失去了作用,此时又要再一次进行优化。

WebAssembly 诞生

所以在 2015 年,我们迎来了 WebAssembly。WebAssembly 是经过编译器编译之后的代码,体积小、起步快。在语法上完全脱离 JavaScript,同时具有沙盒化的执行环境。WebAssembly 同样的强制静态类型,是 C/C++/Rust 的编译目标。

那大家可能会说,为什么我几乎没有听过 WebAssembly 这项技术的实际应用,其实最早用于实际产品的是谷歌,谷歌在谷歌地球中频繁使用这项技术来提高 3D 渲染性能。

WebAssembly 会替代 JS?

不会,WebAssembly 是被设计成 JavaScript 的一个完善、补充,而不是一个替代品。WebAssembly 将很多编程语言带到了 Web 中。但是 JavaScript 因其不可思议的能力,仍然会在很多年之后保持霸主地位。

何时使用?

我们应该在什么时候使用 WebAssembly?

  • 1.对性能有很高要求的 App/Module/游戏
  • 2.在 Web 中使用 C/C++/Rust/Go 的库

第一个 WebAssembly 示例

说了那么多,我们还是依照程序员的惯例 “show code”,我们自己写一个 WebAssembly 示例,来熟悉一下大致的流程和如何实际应用。

开发工具准备

  • AssemblyScript 支持直接将 TypeScript 编译成 WebAssembly。
  • Emscripten 可以说是 WebAssembly 的灵魂工具不为过,上面说了很多编译,这个就是那个编译器。将其他的高级语言,编译成 WebAssembly。
  • WABT 是个将 WebAssembly 在字节码和文本格式相互转换的一个工具,方便开发者去理解这个 wasm 到底是在做什么事。
  • WebAssembly Studio 在线的 WebAssembly 编译工具,可以方便的把我们的代码编译成 wasm 模块文件。

C++代码编写

打开 WebAssembly Studio,创建一个空 C 项目,国际惯例返回 Hello World,以及两个数相加。

输入代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define WASM_EXPORT __attribute__((visibility("default")))
#include <stdio.h>

WASM_EXPORT
int main()
{
return 0;
}

WASM_EXPORT
int sum(int a, int b)
{
return a+b;
}

WASM_EXPORT
char * c_hello()
{
return "Hello World";
}

点击 Build,将会生成一个 wasm 文件,然后右键下载下来,创建一个 html 文件,输入以下代码

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
32
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>

<body>
<h2>Say Hi from WebAssembly</h2>
<span id="hi_container"></span>
<h2>Sum two numbers from WebAssembly</h2>
<span id="sum_container"></span>
<script>
fetch("main.wasm")
.then((bytes) => bytes.arrayBuffer())
.then((mod) => WebAssembly.compile(mod))
.then((module) => {
return new WebAssembly.Instance(module);
})
.then((instance) => {
let buffer = new Uint8Array(instance.exports.memory.buffer);
let test = instance.exports.c_hello();
let mytext = "";
for (let i = test; buffer[i]; i++) {
mytext += String.fromCharCode(buffer[i]);
}
document.getElementById("hi_container").textContent = mytext;
document.getElementById("sum_container").textContent =
instance.exports.sum(100 + 300);
});
</script>
</body>
</html>

运行起来查看效果

评论