有这样一种技术,可以把用高级语言编写的非 Web 程序转换成为 Web 准备的二进制模块,而无需对 Web 程序的源代码进行任何更改即可完成这种转换。浏览器可以有效地下载新翻译的模块并在沙箱中执行。执行的 Web 模块可以与其他 Web 技术无缝地交互 - 特别是 JavaScript(JS)。欢迎来到WebAssembly。
对于名称中带有 assembly 的语言,WebAssembly 是低级的。但是这种低级角色鼓励优化:浏览器虚拟机的即时(JIT)编译器可以将可移植的 WebAssembly 代码转换为快速的、特定于平台的机器代码。因此,WebAssembly 模块成为适用于计算绑定任务(例如数字运算)的可执行文件。
有很多高级语言都能编译成 WebAssembly,而且这个名单正在增长,但最初的候选是C、C ++ 和 Rust。我们将这三种称为系统语言,因为它们用于系统编程和高性能应用编程。系统语言都具有两个特性,这使它们适合被编译为 WebAssembly。下一节将详细介绍设置完整的代码示例(使用 C 和 TypeScript)以及来自 WebAssembly 自己的文本格式语言的示例。
这三种系统语言需要显式数据类型,例如 int 和 double,用于变量声明和从函数返回的值。例如以下代码段说明了 C 中的 64 位加法:
long n1 = random();
long n2 = random();
long sum = n1 + n2;
库函数 random 声明以 long 为返回类型:
long random(); /* returns a long */
在编译过程中,C 源被翻译成汇编语言,然后再将其翻译成机器代码。在英特尔汇编语言(AT&T flavor)中,上面的最后一个 C 语句的功能类似以下内容(## 为汇编语言的注释符号):
addq %rax, %rdx ## %rax = %rax + %rdx (64-bit addition)
%rax 和 %rdx 是 64 位寄存器,addq 指令意味着 add quadwords,其中 quadword 是 64 位大小,这是 C 语言中 long类型的标准大小。汇编语言强调可执行机器代码涉及类型,通过指令和参数的混合给出类型(如果有的话)。在这种情况下,add 指令是 addq(64 位加法),而不是例如 addl 这样的指令,它增加了 C 语言典型的 int 的 32 位值。使用的寄存器字长是完整的 64 位( %rax 和%rdx )而不是其 32 位的(例如,%eax 是 %rax 的低 32 位,%edx 是 %rdx 的低 32 位)。
汇编语言的效果很好,因为操作数被存储在 CPU 寄存器中,而合理的 C 编译器(即使是默认的优化级别)也会生成与此处所示相同的汇编代码。
这三种系统语言强调显式类型,是编译成 WebAssembly 的理想选择,因为这种语言也有明确的数据类型:i32 表示 32 位的整数值,f64 表示 64 位的浮点值,依此类推。
显式数据类型也鼓励优化函数调用。具有显式数据类型的函数具有 signature,它用于指定参数的数据类型以及从函数返回的值(如果有)。下面是名为$add 的 WebAssembly 函数的签名,该函数使用下面讨论的 WebAssembly 文本格式语言编写。该函数把两个 32 位的整数作为参数并返回一个 64 位的整数:
(func $add (param $lhs i32) (param $rhs i32) (result i64))
浏览器的 JIT 编译器应该具有 32 位的整数参数,并把返回的 64 位值存储在适当大小的寄存器中。
谈到高性能 Web 代码,WebAssembly 并不是唯一的选择。例如,asm.js 是一种 JS 方言,与 WebAssembly 一样,可以接近原生速度。 asm.js 方言允许优化,因为代码模仿上述三种语言中的显式数据类型。这是 C 和 am.js 的例子。 C中的示例函数是:
int f(int n) { /** C **/
return n + 1;
}
参数 n 和返回值都以 int 显式输入。asm.js 的等效函数是:
function f(n) { /** asm.js **/
n = n | 0;
return (n + 1) | 0;
}
通常,JS 没有显式数据类型,但 JS 中的按位或运算符能够产生一个整数值。这就解释了看上去毫无意义的按位或运算符:
n = n | 0; /* bitwise-OR of n and zero */
n 和 0 之间的按位或运算得到 n,但这里的目的是表示 n 保持整数值。 return 语句重复了这个优化技巧。
在 JS 方言中,TypeScript 在显式数据类型方面脱颖而出,这使得这种语言对于编译成 WebAssembly 很有吸引力。 (下面的代码示例说明了这一点。)
三种系统语言都具有的第二个特性是它们在没有垃圾收集器(GC)的情况下执行。对于动态分配的内存,Rust 编译器会自动分配和释放代码;在其他两种系统语言中,动态分配内存的程序员负责显式释放内存。系统语言避免了自动化 GC 的开销和复杂性。
WebAssembly 的概述可以总结如下。几乎所有关于 WebAssembly 语言的文章都提到把近乎原生的速度作为语言的主要目标之一。 原生速度是指已编译的系统语言的速度,因此这三种语言也是最初被指定为编译成 WebAssembly 的候选者的原因。
WebAssembly 语言并非为了取代 JS,而是为了通过在计算绑定任务上提供更好的性能来补充 JS。WebAssembly 在下载方面也有优势。浏览器将 JS 模块作为文本提取,这正是 WebAssembly 能够解决的低效率问题之一。WebAssembly 中的模块是紧凑的二进制格式,可加快下载速度。
同样令人感兴趣的是 JS 和 WebAssembly 如何协同工作。 JS 旨在读入文档对象模型(DOM),即网页的树形表示。相比之下,WebAssembly 没有为 DOM 提供任何内置功能,但是 WebAssembly 可以导出 JS 根据需要调用的函数。这种关注点分离意味着清晰的分工:
DOM<----->JS<----->WebAssembly
无论用什么方言,JS 都应该管理 DOM,但 JS 也可以用通过 WebAssembly 模块提供的通用功能。代码示例有助于说明,本文中的代码案例可以在我的网站上找到(http://condor.depaul.edu/mkalin)。
生产级代码案例将使 WebAssembly 代码执行繁重的计算绑定任务,例如生成大型加密密钥对,或进行加密和解密。
考虑函数 hstone(对于hailstone),它以正整数作为参数。该函数定义如下:
3N + 1 if N is odd
hstone(N) =
N/2 if N is even
例如,hstone(12) 返回 6,而 hstone(11) 返回 34。如果 N 是奇数,则 3N + 1 是偶数;但如果 N 是偶数,则 N/2 可以是偶数(例如,4/2 = 2)或奇数(例如,6/2 = 3)。
hstone 函数可以通过将返回值作为下一个参数传递来进行迭代。结果是一个 hailstone 序列,例如这个序列,以 24 作为原始参数开始,返回值 12 作为下一个参数,依此类推:
24,12,6,3,10,5,16,8,4,2,1,4,2,1,...
序列收敛到 4,2,1 的序列无限重复需要 10 次调用:(3 x 1)+ 1 是 4,它除以 2 得 2,再除以 2 得 1。 Plus 杂志提供了为什么把这些序列的称做 hailstone 的解释。
请注意,两个幂很快收敛,只需要 N 除以 2 得到 1;例如,32 = 25的收敛长度为5,64 = 26的收敛长度为6。这里感兴趣的是从初始参数到第一个出现的序列长度。我在 C 和 TypeScript 中的代码例子计算了冰雹序列的长度。
Collatz 猜想是一个冰雹序列会收敛到 1,无论初始值 N> 0 恰好是什么。没有人找到 Collatz 猜想的反例,也没有人找到证据将猜想提升到一个定理。这个猜想很简单,就像用程序测试一样,是数学中一个极具挑战性的问题。
下面的 hstoneCL 程序是一个非 Web 应用,可以使用常规 C 语言编译器(例如,GNU 或 Clang)进行编译。程序生成一个随机整数值 N> 0 八次,并计算从 N 开始的冰雹序列的长度。两个程序员定义的函数,main 和 hstone 是有意义的。该应用程序稍后会被编译为 WebAssembly。
示例1. C 中的 hstone 函数
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int hstone(int n) {
int len = 0;
while (1) {
if (1 == n) break; /* halt on 1 */
if (0 == (n & 1)) n = n / 2; /* if n is even */
else n = (3 * n) + 1; /* if n is odd */
len++; /* increment counter */
}
return len;
}
#define HowMany 8
int main() {
srand(time(NULL)); /* seed random number generator */
int i;
puts(" Num Steps to 1");
for (i = 0; i < HowMany; i++) {
int num = rand() % 100 + 1; /* + 1 to avoid zero */
printf("%4i %7i\n", num, hstone(num));
}
return 0;
}
代码可以在任何类 Unix 系统上从命令行编译和运行(% 是命令行提示符):
% gcc -o hstoneCL hstoneCL.c ## compile into executable hstoneCL
% ./hstoneCL ## execute
以下是例子运行的输出:
Num Steps to 1
88 17
1 0
20 7
41 109
80 9
84 9
94 105
34 13
系统语言(包括 C)需要专门的工具链才能将源代码转换为 WebAssembly 模块。对于 C/C++ 语言,Emscripten 是一个开创性且仍然广泛使用的选项,建立在众所周知的 LLVM (低级虚拟机)编译器基础结构之上。我在 C 语言中的示例使用 Emscripten,你可以[使用本指南进行安装(https://github.com/emscripten...)。
hstoneCL 程序可以通过使用 Emscription 编译代码进行 Web 化,而无需任何更改。Emscription工具链还与 JS glue(在asm.js中)一起创建一个html页面,该页面介于 DOM 和计算 hstone 函数的 WebAssembly 模块之间。以下是步骤:
% emcc hstoneCL.c -o hstone.html ## generates hstone.js and hstone.wasm as well
文件 hstoneCL.c 中包含上面显示的源代码,-o 输出标志用于指定 HTML 文件的名称。任何名称都可以,但生成的 JS 代码和 WebAssembly 二进制文件具有相同的名称(在本例中,分别为 hstone.js 和 hstone.wasm)。较旧版本的 Emscription(在13之前)可能需要将标志 -s WASM = 1 包含在编译命令中。
% emrun --no_browser --port 9876 . ## . is current working directory, any port number you like
要禁止显示警告消息,可以包含标志 --no_emrun_detect。此命令用于启动 Web 服务器,该服务器承载当前工作目录中的所有资源;特别是 hstone.html、hstone.js 和 hstone.webasm。
这个截图显示了我用 Firefox 运行的示例输出。
结果非常显著,因为完整的编译过程只需要一个命令,而且不需要对原始 C 程序进行任何更改。
Emscription工具链很好地将 C 程序编译成 WebAssembly 模块并生成所需的 JS 胶水,但这些是机器生成的典型代码。例如,生成的 asm.js 文件大小几乎为 100 KB。 JS 代码处理多个场景,并且不使用最新的 WebAssembly api。 webified hstone 程序的简化版本将使你更容易关注 WebAssembly 模块(位于 hstone.wasm 文件中)如何与 JS 胶水(位于 hstone.js 文件中)进行交互。
还有另一个问题:WebAssembly 代码不需要镜像 C 等源程序中的功能边界。例如,C 程序 hstoneCL 有两个用户定义的函数,main 和 hstone。生成的 WebAssembly 模块导出名为 _ main 的函数,但不导出名为 _ hstone 的函数。 (值得注意的是,函数 main 是 C 程序中的入口点。)C 语言 hstone 函数的主体可能在某些未导出的函数中,或者只是包含在 _ main中。导出的 WebAssembly 函数正是 JS glue 可以通过名称调用的函数。但是应在 WebAssembly 代码中按名称导出哪些源语言函数。
示例2. 修订后的 hstone 程序
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <emscripten/emscripten.h>
int EMSCRIPTEN_KEEPALIVE hstone(int n) {
int len = 0;
while (1) {
if (1 == n) break; /* halt on 1 */
if (0 == (n & 1)) n = n / 2; /* if n is even */
else n = (3 * n) + 1; /* if n is odd */
len++; /* increment counter */
}
return len;
}
如上所示,修改后的 hstoneWA 程序没有 main 函数,它不再需要,因为该程序不是作为独立程序运行,而是仅作为具有单个导出函数的 WebAssembly 模块运行。指令 EMSCRIPTEN_KEEPALIVE(在头文件 emscripten.h 中定义)指示编译器在 WebAssembly 模块中导出 _ hstone 函数。命名约定很简单:诸如 hstone 之类的 C 函数保留其名称 —— 但在 WebAssembly 中使用单个下划线作为其第一个字符(在本例中为 _ hstone)。 WebAssembly中的其他编译器遵循不同的命名约定。
要确认此方法是否有效,可以简化编译步骤,仅生成 WebAssembly 模块和 JS 粘合剂而不是 HTML:
% emcc hstoneWA.c -o hstone2.js ## we'll provide our own HTML file
HTML文件现在可以简化为这个手写的文件:
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<script src="hstone2.js"></script>
</head>
<body/>
</html>
HTML 文档加载 JS 文件,后者又获取并加载 WebAssembly 二进制文件 hstone2.wasm。顺便说一下,新的 WASM 文件大小只是原始例子的一半。
程序代码可以像以前一样编译,然后使用内置的Web服务器启动:
% emrun --no_browser --port 7777 . ## new port number for emphasis
在浏览器(在本例中为 Chrome)中请求修改后的 HTML 文档后,可以用浏览器的 Web 控制台确认 hstone 函数已导出为 _ hstone。以下是我在 Web 控制台中的会话段,## 为注释符号:
> _hstone(27) ## invoke _hstone by name
< 111 ## output
> _hstone(7) ## again
< 16 ## output
EMSCRIPTEN_KEEPALIVE 指令是使 Emscripten 编译器生成 WebAssembly 模块的简单方法,该模块将所有感兴趣的函数导出到 JS 编程器同样产生的 JS 粘合剂。一个自定义的 HTML 文档,无论手写的 JS 是否合适,都可以调用从 WebAssembly 模块导出的函数。为了这个干净的方法,向 Emscripten 致敬。
下一个代码示例是 TypeScript,它是具有显式数据类型的 JS。该设置需要 Node.js 及其 npm 包管理器。以下 npm 命令安装 AssemblyScript,它是 TypeScript 代码的 WebAssembly 编译器:
% npm install -g assemblyscript ## install the AssemblyScript compiler
TypeScript 程序 hstone.ts 由单个函数组成,同样名为 hstone。现在数据类型如 i32(32位整数)紧跟参数和局部变量名称(在本例中分别为 n 和 len):
export function hstone(n: i32): i32 { // will be exported in WebAssembly
let len: i32 = 0;
while (true) {
if (1 == n) break; // halt on 1
if (0 == (n & 1)) n = n / 2; // if n is even
else n = (3 * n) + 1; // if n is odd
len++; // increment counter
}
return len;
}
函数 hstone 接受一个 i32 类型的参数,并返回相同类型的值。函数的主体与 C 语言示例中的主体基本相同。代码可以编译成 WebAssembly,如下所示:
% asc hstone.ts -o hstone.wasm ## compile a TypeScript file into WebAssembly
WASM 文件 hstone.wasm 的大小仅为14 KB。
要突出显示如何加载 WebAssembly 模块的详细信息,下面的手写 HTML 文件(我的网站上找到(http://condor.depaul.edu/mkalin)中的 index.html)包含以下脚本:获取并加载 WebAssembly 模块 hstone.wasm然后实例化此模块,以便可以在浏览器控制台中调用导出的 hstone 函数进行确认。
示例 3. TypeScript 代码的 HTML页面
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<script>
fetch('hstone.wasm').then(response => <!-- Line 1 -->
response.arrayBuffer() <!-- Line 2 -->
).then(bytes => <!-- Line 3 -->
WebAssembly.instantiate(bytes, {imports: {}}) <!-- Line 4 -->
).then(results => { <!-- Line 5 -->
window.hstone = results.instance.exports.hstone; <!-- Line 6 -->
});
</script>
</head>
<body/>
</html>
上面的 HTML 页面中的脚本元素可以逐行说明。第 1 行中的 fetch 调用使用 Fetch 模块从托管 HTML 页面的 Web 服务器获取 WebAssembly 模块。当 HTTP 响应到达时,WebAssembly 模块将把它做作为一个字节序列,它存储在脚本第 2 行的 arrayBuffer 中。这些字节构成了 WebAssembly 模块,它是从 TypeScript 编译的代码。文件。该模块没有导入,如第 4 行末尾所示。
在第 4 行的开头实例化 WebAssembly 模块。 WebAssembly 模块类似于非静态类,其中包含面向对象语言(如Java)中的非静态成员。该模块包含变量、函数和各种支持组件;但是与非静态类一样,模块必须实例化为可用,在本例中是在 Web 控制台中,但更常见的是在相应的 JS 粘合代码中。
脚本的第 6 行以相同的名称导出原始的 TypeScript 函数 hstone。此 WebAssembly 功能现在可用于任何 JS 粘合代码,因为在浏览器控制台中的另一个会话将确认。
WebAssembly 具有更简洁的 API,用于获取和实例化模块。新 API 将上面的脚本简化为 fetch 和 instantiate 操作。这里展示的较长版本具有展示细节的好处,特别是将 WebAssembly 模块表示为字节数组,将其实例化为具有导出函数的对象。
计划是让网页以与 JS ES2015 模块相同的方式加载 WebAssembly 模块:
<script type='module'>...</script>
然后,JS 将获取、编译并以其他方式处理 WebAssembly 模块,就像是加载另一个 JS 模块一样。
WebAssembly 二进制文件可以转换为 文本格式的等价物。二进制文件通常驻留在具有 WASM 扩展名的文件中,而其人类可读的文本副本驻留在具有 WAT 扩展名的文件中。 WABT 是一套用于处理 WebAssembly 的工具,其中包括用于转换为 WASM 和 WAT 格式的工具。转换工具包括 wasm2wat,wasm2c 和 wat2wasm 等。
文本格式语言采用 Lisp 推广的 S 表达式(S for symbolic)语法。 S 表达式(简称 sexpr)表示把树作为具有任意多个子列表的列表。例如这段 sexpr 出现在 TypeScript 示例的 WAT 文件末尾附近:
(export "hstone" (func $hstone)) ## export function $hstone by the name "hstone"
树表示是:
export ## root
|
+----+----+
| |
"hstone" func ## left and right children
|
$hstone ## single child
在文本格式中,WebAssembly 模块是一个 sexpr,其第一项是模块,它是树的根。下面是一个定义和导出单个函数的模块的简单例子,该函数不带参数但返回常量 9876:
(module
(func (result i32)
(i32.const 9876)
)
(export "simpleFunc" (func 0)) // 0 is the unnamed function's index
)
该函数的定义没有名称(即作为 lambda),并通过引用其索引 0 导出,索引 0 是模块中第一个嵌套的 sexpr 的索引。导出名称以字符串形式给出;在当前情况下其名称为“simpleFunc”。
文本格式的函数具有标准模式,可以如下所示:
(func <signature> <local vars> <body>)
签名指定参数(如果有)和返回值(如果有)。例如,这是一个未命名函数的签名,它接受两个 32 位整数参数,返回一个 64 位整数值:
(func (param i32) (param i32) (result i64)...)
名称可以赋予函数、参数和局部变量。名称以美元符号开头:
(func $foo (param $a1 i32) (param $a2 f32) (local $n1 f64)...)
WebAssembly 函数的主体反映了该语言的底层栈机器体系结构。栈存储用于暂存器。考虑一个函数的示例,该函数将其整数参数加倍并返回:
(func $doubleit (param $p i32) (result i32)
get_local $p
get_local $p
i32.add)
每个 get_local 操作都可以处理局部变量和参数,将 32 位整数参数压入栈。然后 i32.add 操作从栈中弹出前两个(当前唯一的)值以执行添加。最后 add 操作的和是栈上的唯一值,从而成为 $doubleit 函数的返回的值。
当 WebAssembly 代码转换为机器代码时,WebAssembly 栈作为暂存器应尽可能由通用寄存器替换。这是 JIT 编译器的工作,它将 WebAssembly 虚拟栈机器代码转换为实际机器代码。
Web 程序员不太可能以文本格式编写 WebAssembly,因为从某些高级语言编译是一个非常有吸引力的选择。相比之下,编译器编的作者可能会发现在这种细粒度级别上工作是有效的。
WebAssembly 的目标是实现近乎原生的速度。但随着 JS 的 JIT 编译器不断改进,并且随着非常适合优化的方言(例如,TypeScript)的出现和发展,JS 也可能实现接近原生的速度。这是否意味着 WebAssembly 是在浪费精力?我想不是。
WebAssembly 解决了计算中的另一个传统目标:有意义的代码重用。正如本文中的例子所示,使用适当语言(如 C 或 TypeScript)的代码可以轻松转换为 WebAssembly 模块,该模块可以很好地与 JS 代码一起使用 —— 这是连接 Web 中所使用的一系列技术的粘合剂。因此 WebAssembly 是重用遗留代码和扩展新代码使用的一种诱人方式。例如最初作为桌面应用的用于图像处理的高性能程序在 Web 应用中也可能是有用的。然后 WebAssembly 成为重用的有吸引力的途径。 (对于计算限制的新 Web 模块,WebAssembly 是一个合理的选择。)我的预感是 WebAssembly 将在重用和性能方面茁壮成长。
原文:https://opensource.com/article/19/8/webassembly-speed-code-reuse
这是专门探索JavaScript及其构建组件的系列,在识别和描述核心元素的过程中,我们还分享了构建SessionStack时使用的一些经验法则,这是一个轻量级但健壮且高性能的JavaScript应用程序,以帮助用户实时查看和重现其Web应用程序的缺陷。
作为JavaScript替代,一种Web开发的新形式已经浮出水面:WebAssembly.Web开发与JavaScript开发向来是同义词。就是说,直到现在。但一种新的Web开发形式已然出现,声言会取代JavaScript
在经过26次金丝雀发布和340万次下载之后,现在,我们正式推出生产就绪的Next.js 7。DX改进:启动速度提高57%,重新编译速度提高42%;使用react-error-overlay更好地报告错误;编译管道升级:Webpack 4和Babel 7;标准化的动态导入;静态CDN支持;
WebAssembly 在2017年受到主流浏览器的支持,并发布了 MVP 版本,为消除人们对 WebAssembly 的误解,WebAssembly 社区组以 RPG 游戏中人物养成的“技能树”形式,对 WebAssembly 的未来发展路径做了非常详细的解释。
为了能够让其他语言的代码在浏览器中运行,WebAssembly被创造出来。它拥有更好性能,更小的size,能够更快的加载和执行。我们无需编写WebAssembly的代码,只需要将其他高级语言编译成WebAssembly,这样就能在浏览器中复用大量的其他语言现有的代码。
接触WebAssembly之后,在google上看了很多资料。感觉对WebAssembly的使用、介绍、意义都说的比较模糊和笼统。感觉看了之后收获没有达到预期,要么是文章中的例子自己去实操不能成功,要么就是不知所云
WebAssembly(缩写WASM)是一种安全,便携,低级代码设计用于高效执行和紧凑表示的格式。它的主要目标是使Web上的高性能应用,不需要针对网络的特定假设或提供特定的定制化的网络功能
长期以来,Python 社区一直在讨论如何使 Python 成为网页浏览器中流行的编程语言。然而网络浏览器实际上只支持一种编程语言:JavaScript。随着网络技术的发展
内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!