先看下 官网 给的定义。
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
WebAssembly
是基于栈式虚拟机的二进制指令集,可以作为编程语言的编译目标,能够部署在 web
客户端和服务端的应用中。
第一次看到这个定义的时候是一头雾水,翻了一些资料渐渐有了点轮廓,下边分享下我目前的理解。
首先 WebAssembly
是由 Web
和 Assembly
两个词构成,其中 Web
表明它一定和前端有关。Assembly
的意思是汇编,汇编对应机器码,而机器码和 CPU
的指令集有关,接下来补一下相关的知识。
其中指令集、操作系统相关的知识,之前总结过几篇文章,到底学哪一门编程语言、x86,x64,x86-64,amd64,arm指令集架构之间的关系、linux和Android的关系,可以先过去看一下,这里的话抽主要的部分回顾一下。
参考上图,计算机的主要架构如上。最底层是 CPU
的指令集,主要分为复杂指令集和简单指令集。
复杂指令集是 x86
、x64(也叫 x86-64, amd64)
两种架构,专利在 Intel
和 AMD
两家公司手里, 该架构 CPU
主要是 Intel
和 AMD
两家公司,这种 CPU
常用在 PC
机上,包括 Windows
,macOS
和 Linux
。
简单指令集是 arm
一种架构,专利在 ARM
公司手里,该架构 CPU
主要有高通、三星、苹果、华为海思、联发科等公司。这种 CPU
常用在手机上,包括安卓和苹果。
指令集是什么呢?直接把阮一峰的老师的一个 例子 粘过来,大家可以看一下。
c
语言的源程序。
int add_a_and_b(int a, int b) {
return a + b;
}
int main() {
return add_a_and_b(2, 3);
}
所对应的汇编就是下边的样子。
_add_a_and_b:
push %ebx
mov %eax, [%esp+8]
mov %ebx, [%esp+12]
add %eax, %ebx
pop %ebx
ret
_main:
push 3
push 2
call _add_a_and_b
add %esp, 8
ret
这里的 push
、mov
每一条指令就是指令集规定的内容,规定了操作码、操作数以及具体的功能。当然这里是用汇编表示的,主要是为了我们人类来读写,最终还会转成 0,1
序列。上边每个单词都会有一个数字相对应,比如 add
指令对应 00000011
。
通过规定的指令集(加法的指令,压栈指令等),编写相关程序,然后 CPU
就会一条一条的执行,最终实现相应的功能。
而 WebAssembly
就规定了一套指令集,更准确的来说是虚拟指令集,因为这套指令集是跑在虚拟机上的,而不是直接由硬件运行。
上边我们知道了 WebAssembly
的 Assembly
,即汇编,也就是指令集。下边在回顾下 Web
,即 WebAssembly
诞生的原因。
这里就得谈到 javaScript
了,众所周知, javaScript
是一门动态类型的语言,编写程序时无需考虑变量类型,而且还可以运行时改变类型。对于我们开发者,确实很方便,但对于运行它的引擎就很有问题了。参考 这里 的一张图,看一下 V8
引擎从 js
源码到执行的一个过程。
由于 js
的动态类型,解释器在执行代码的时候会在类型判断上带来一定的性能消耗,降低执行速度。所以 V8
引擎采用了 JIT
(即时编译技术) 技术,监控一些经常执行的代码,将其编译成 CPU
直接执行的机器码,提高执行速度。但由于 js
动态类型,在某些情况下还得反优化,回到字节码进行执行。
随着前端的不断发展,项目的大小和复杂度不断增大,对于某些场景,性能上可能已经无法满足,浏览器厂商们也一直在探索性能优化的方法。
2011
年 Google
在 Chrome
中使用了 NaCl
技术,可以使得 C
语言编写的程序运行到浏览器中,下边是维基百科 的定义。
Google Native Client(缩写为NaCl),是一个由谷歌所发起的开放源代码计划,采用BSD许可证。它采用沙盒技术,让Intel x86、ARM或MIPS子集的机器代码直接在沙盒上运行。它能够从浏览器直接运行程序机器代码,独立于用户的操作系统之外,使Web应用程序可以用接近于机器代码运作的速度来运行,同时兼顾安全性。其功能类似于微软的 ActiveX,但是ActiveX只支持视窗系统。
但一个完整的 NaCl
应用,在分发时需要提供支持多个架构平台(X86 / X64 / ARM 等)的模块文件,后来谷歌又推出了与底层架构无关的 PNaCl
技术。但由于其开发难度、兼容性等问题最终没有普及开来。在 2017
年 Google
宣布放弃 PNaCl
转向 WebAssembly
。
ASM.js
是 Mozilla
在 2013
年推出的,是 javaScript
的一个严格子集,可以作为 C/C++
编译的目标语言,从而使得 js
引擎可以采用 AOT(Ahead Of Time)
的编译策略,也就是在运行前直接编译成机器码,因此运行速度会有一定的提升。
ASM.js
通常不直接编写,而是作为一种通过编译器生成的中间语言,该编译器获取 C++
或其他语言的源代码,然后输出 ASM.js
。
例如下边的 C
语言代码。
int f(int i) {
return i + 1;
}
经过编译器编译会生成下边的 js
代码。
function f(i) {
i = i|0;
return (i + 1)|0;
}
注意这里的|0
在 js
中相当于和 0
进行了或操作,所以不影响原本的逻辑。在 asm.js
中起到了类型标记的作用,这样 js
引擎执行的时候就知道 i
是一个整型,返回值是一个整型。除了或操作这种,ASM.js
标准中还规定了很多类似的标记规则,用于告诉 js
引擎变量的类型,便于进行 AOT
优化。
这看起来和 TypeScript
很像,但其实不是一种东西。TypeScript
是 js
的一个超集,浏览器并不能直接执行 ts
,还需要转换为 js
去执行。ts
主要是帮助我们开发人员去看的,增加了代码的可读性,也可以让编辑器提前发现一些错误。而 asm.js
是用于引擎的编译优化。
接下来看一下 WebAssembly
的历史。
2015 年 4 月,WebAssembly Community Group 成立;
2015 年 6 月,WebAssembly 第一次以 WCG 的官方名义向外界公布;
2016 年 8 月,WebAssembly 开始进入了漫长的 “Browser Preview” 阶段;
2017 年 2 月,WebAssembly 官方 LOGO 在 Github 上的众多讨论中被最终确定;同年同月,一个历史性的阶段,四大浏览器(FireFox、Chrome、Edge、WebKit)在 WebAssembly 的 MVP(最小可用版本)标准实现上达成共识,这意味着 WebAssembly 在其 MVP 标准上的 “Brower Preview” 阶段已经结束;
2017 年 8 月,W3C WebAssembly Working Group 成立,意味着 WebAssembly 正式成为 W3C 众多技术标准中的一员。
WebAssembly
于 2019
年 12
月 5
日成为万维网联盟(W3C
)的推荐标准,与 HTML
,CSS
和 JavaScript
一起成为 Web
的第四种语言。
可以看一下目前浏览器的支持程度,已经算比较高了。
目前已经有了将 C/C++
、Rust
、ts
、C#
、Go
、Kotlin
、Swift
等语言转换为 WebAssembly(wasm)
的工具,下边我们体验一下 C++
转换的过程。
首先编写一个 C++
程序 fibonacci.cc
,斐波纳契数字的递归写法。
#include
extern "C" {
EMSCRIPTEN_KEEPALIVE
int fibonacci(int n) {
if(n < 2) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
}
函数的定义置在 extern “C” {}
结构中,是为了防止函数名编译后被改变。EMSCRIPTEN_KEEPALIVE
是为了确保函数不会在编译器的编译过程中,被 DCE(Dead Code 」limination)
过程处理掉。
然后需要安装 Emscripten
用来将 C++
程序编译为 WebAssembly(wasm)
的程序,安装后执行下边的命令。
emcc fibonacci.cc -s WASM=1 -O3 --no-entry -o fibonacci.wasm
-s WASM=1
表明编译成 Webassembly
的程序,-O3
表明编译的优化程度,–no-entry
参数告诉编译器没有声明 main
函数,-o
指定生成的文件名。
让我们看一下生成的字节码文件 fibonacci.wasm
。
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 00 61 73 6D 01 00 00 00 01 11 04 60 00 01 7F 60 .asm.......`...`
00000010: 01 7F 01 7F 60 00 00 60 01 7F 00 03 07 06 02 01 ....`..`........
00000020: 00 03 01 00 04 05 01 70 01 02 02 05 06 01 01 80 .......p........
00000030: 02 80 02 06 0F 02 7F 01 41 90 88 C0 02 0B 7F 00 ........A..@....
00000040: 41 84 08 0B 07 88 01 09 06 6D 65 6D 6F 72 79 02 A........memory.
00000050: 00 19 5F 5F 69 6E 64 69 72 65 63 74 5F 66 75 6E ..__indirect_fun
00000060: 63 74 69 6F 6E 5F 74 61 62 6C 65 01 00 09 66 69 ction_table...fi
00000070: 62 6F 6E 61 63 63 69 00 01 0B 5F 69 6E 69 74 69 bonacci..._initi
00000080: 61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F alize...__errno_
00000090: 6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B location...stack
000000a0: 53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74 Save...stackRest
000000b0: 6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63 ore...stackAlloc
000000c0: 00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09 ...__data_end...
000000d0: 07 01 00 41 01 0B 01 00 0A 66 06 03 00 01 0B 3D ...A.....f.....=
000000e0: 01 02 7F 41 01 21 01 20 00 41 02 4E 04 7F 41 00 ...A.!...A.N..A.
000000f0: 21 01 03 40 20 00 41 7F 6A 10 01 20 01 6A 21 01 !..@..A.j....j!.
00000100: 20 00 41 03 4A 21 02 20 00 41 7E 6A 21 00 20 02 ..A.J!...A~j!...
00000110: 0D 00 0B 20 01 41 01 6A 05 41 01 0B 0B 04 00 23 .....A.j.A.....#
00000120: 00 0B 06 00 20 00 24 00 0B 10 00 23 00 20 00 6B ......$....#...k
00000130: 41 70 71 22 00 24 00 20 00 0B 05 00 41 80 08 0B Apq".$......A...
让我们来解读下,最开始的前八个字节 0x0 0x61 0x73 0x6d 0x1 0x0 0x0 0x0
表明当前是一个 wasm
的模块。然后会分很多 Section
,Function Section
, Code Section
等等,都有特定的数字对应,还有就是文章开头讲的指令操作符所对应的一些数字。
看着上边的字节码仿佛回到了上古时期直接用机器码编程的时代,当年出现了汇编语言。这里也会有类似汇编的东西,那就是 WAT(WebAssembly Text Format)
。
需要安装 WABT , 然后执行 wasm2wat
命令。
../wabt/bin/wasm2wat fibonacci.wasm -o fibonacci.wat
然后就生成了 fibonacci.wat
文件。
(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32) (result i32)))
(type (;2;) (func))
(type (;3;) (func (param i32)))
(func (;0;) (type 2)
nop)
(func (;1;) (type 1) (param i32) (result i32)
(local i32 i32)
i32.const 1
local.set 1
local.get 0
i32.const 2
i32.ge_s
if (result i32) ;; label = @1
i32.const 0
local.set 1
loop ;; label = @2
local.get 0
i32.const -1
i32.add
call 1
local.get 1
i32.add
local.set 1
local.get 0
i32.const 3
i32.gt_s
local.set 2
local.get 0
i32.const -2
i32.add
local.set 0
local.get 2
br_if 0 (;@2;)
end
local.get 1
i32.const 1
i32.add
else
i32.const 1
end)
(func (;2;) (type 0) (result i32)
global.get 0)
(func (;3;) (type 3) (param i32)
local.get 0
global.set 0)
(func (;4;) (type 1) (param i32) (result i32)
global.get 0
local.get 0
i32.sub
i32.const -16
i32.and
local.tee 0
global.set 0
local.get 0)
(func (;5;) (type 0) (result i32)
i32.const 1024)
(table (;0;) 2 2 funcref)
(memory (;0;) 256 256)
(global (;0;) (mut i32) (i32.const 5243920))
(global (;1;) i32 (i32.const 1028))
(export "memory" (memory 0))
(export "__indirect_function_table" (table 0))
(export "fibonacci" (func 1))
(export "_initialize" (func 0))
(export "__errno_location" (func 5))
(export "stackSave" (func 2))
(export "stackRestore" (func 3))
(export "stackAlloc" (func 4))
(export "__data_end" (global 1))
(elem (;0;) (i32.const 1) func 0))
上边的格式属于 「S- 表达式」, Lisp
语言就是采用的这种表达式,每条语句都是先执行最里边括号的表达式然后依次展开。
上边主要介绍了 .wasm
具体长什么样子,下边看一下怎么用到浏览器中。
从 .wasm
源文件到实例化的对象主要有三个步骤,加载 -> 编译 -> 实例化 -> 调用
。
加载:读取 .wasm
字节码到本地中,一般是通过 fetch
从网络中取得。
编译:在 Worker 线程进行,编译成平台相关的代码。
实例化:将宿主环境的一些对象、方法导入到 wasm
模块中,比如导入操作 dom
的方法。
调用:通过上一步已经实例化的对象,来调用 wasm
模块中的方法。
主要有两种类型的 API
,一种是 js
提供的 api
,另一种是 Web
提供的 api
,Web
提供的 api
支持流式编译实例化。
js
的方法,WebAssembly.instantiate(bufferSource, importObject)
,可以完成编译和实例化。
bufferSource
是含有效 Wasm
模块二进制字节码的 ArrayBuffer
或 TypedArray
对象。
importObject
是要导入到 Wasm
模块中的对象。
方法在调用后返回一个Promise
对象,resolve
后返回一个对象,该对象包含编译好的 module
和已经实例化的 instance
,模块导出的方法可以通过 instance
对象进行调用。
web
的方法,WebAssembly.instantiateStreaming(source, importObject)
。
不同之处在于第一个参数,这里的 source
指的是尚未 Resolve
的 Response
对象(window.fetch
调用后会返回该对象),好处就是可以边读取 .wasm
字节流,边进行编译。
其他参数和返回值和 js
的 api
均一致。
先简单的尝试一下,我们直接构造一个 wasm
模块的 TypedArray
对象,该模块包含了一个 add
方法,然后调用 WebAssembly.instantiate
进行编译和实例化。
对应的 C++
代码。
#include
extern "C" {
EMSCRIPTEN_KEEPALIVE
int add(int a, int b) {
return a + b;
}
}
对应的 .wasm
字节码。
00 61 73 6D 01 00 00 00 01 17 05 60 00 01 7F 60
00 00 60 01 7F 00 60 01 7F 01 7F 60 02 7F 7F 01
7F 03 07 06 01 04 00 02 03 00 04 05 01 70 01 02
02 05 06 01 01 80 02 80 02 06 0F 02 7F 01 41 90
88 C0 02 0B 7F 00 41 84 08 0B 07 82 01 09 06 6D
65 6D 6F 72 79 02 00 19 5F 5F 69 6E 64 69 72 65
63 74 5F 66 75 6E 63 74 69 6F 6E 5F 74 61 62 6C
65 01 00 03 61 64 64 00 01 0B 5F 69 6E 69 74 69
61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F
6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B
53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74
6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63
00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09
07 01 00 41 01 0B 01 00 0A 30 06 03 00 01 0B 07
00 20 00 20 01 6A 0B 04 00 23 00 0B 06 00 20 00
24 00 0B 10 00 23 00 20 00 6B 41 70 71 22 00 24
00 20 00 0B 05 00 41 80 08 0B
然后直接在控制台输入下边的代码。
WebAssembly.instantiate(new Uint8Array(`
00 61 73 6D 01 00 00 00 01 17 05 60 00 01 7F 60
00 00 60 01 7F 00 60 01 7F 01 7F 60 02 7F 7F 01
7F 03 07 06 01 04 00 02 03 00 04 05 01 70 01 02
02 05 06 01 01 80 02 80 02 06 0F 02 7F 01 41 90
88 C0 02 0B 7F 00 41 84 08 0B 07 82 01 09 06 6D
65 6D 6F 72 79 02 00 19 5F 5F 69 6E 64 69 72 65
63 74 5F 66 75 6E 63 74 69 6F 6E 5F 74 61 62 6C
65 01 00 03 61 64 64 00 01 0B 5F 69 6E 69 74 69
61 6C 69 7A 65 00 00 10 5F 5F 65 72 72 6E 6F 5F
6C 6F 63 61 74 69 6F 6E 00 05 09 73 74 61 63 6B
53 61 76 65 00 02 0C 73 74 61 63 6B 52 65 73 74
6F 72 65 00 03 0A 73 74 61 63 6B 41 6C 6C 6F 63
00 04 0A 5F 5F 64 61 74 61 5F 65 6E 64 03 01 09
07 01 00 41 01 0B 01 00 0A 30 06 03 00 01 0B 07
00 20 00 20 01 6A 0B 04 00 23 00 0B 06 00 20 00
24 00 0B 10 00 23 00 20 00 6B 41 70 71 22 00 24
00 20 00 0B 05 00 41 80 08 0B`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16))
)).then(({instance}) => {
const { add } = instance.exports
console.log('2 + 4 =', add(2, 4))
})
然后就会看到输出了 2 + 4 = 6
。
我们再尝试一下流式编译。直接使用之前的斐波纳契数字的 fibonacci.wasm
模块。
首先我们需要提供一个简单的 HTTP
服务,用来返回 .wasm
文件。
新建一个 node.js
文件。
const http = require('http');
const url = require('url');
const fs = require('fs');
const path =require('path');
const PORT = 8888; // 服务器监听的端口号;
const mime = {
"html": "text/html;charset=UTF-8",
"wasm": "application/wasm" // 当遇到对 ".wasm" 格式文件的请求时,返回特定的 MIME 头;
};
http.createServer((req, res) => {
let realPath = path.join(__dirname, `.${url.parse(req.url).pathname}`);
// 检查所访问文件是否存在,且是否可读;
fs.access(realPath, fs.constants.R_OK, err => {
if (err) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end();
} else {
fs.readFile(realPath, "binary", (err, file) => {
if (err) {
// 文件读取失败时返回 500;
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end();
} else {
// 根据请求的文件返回相应的文件内容;
let ext = path.extname(realPath);
ext = ext ? ext.slice(1) : 'unknown';
let contentType = mime[ext] || "text/plain";
res.writeHead(200, { 'Content-Type': contentType });
res.write(file, "binary");
res.end();
}
});
}
});
}).listen(PORT);
console.log("Server is runing at port: " + PORT + ".");
然后来编写我们的 html
文件,讲到斐波那契数字,我们顺便做一个性能的测试,来比较一下使用 wasm
的方式和原生 js
的求解速度。
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>斐波纳切数字title>
head>
<script>
function fibonacciJS(n) {
if (n < 2) {
return 1;
}
return fibonacciJS(n - 1) + fibonacciJS(n - 2);
}
const response = fetch("fibonacci.wasm");
const num = [5, 15, 25, 35, 45];
WebAssembly.instantiateStreaming(response).then(
({ instance }) => {
let { fibonacci } = instance.exports;
for(let n of num) {
console.log(`斐波纳切数字: ${n},运行 10 次`)
let cTime = 0;
let jsTime = 0;
for(let time = 0; time < 10; time++) {
let start = performance.now();
fibonacci(n)
cTime += (performance.now() - start)
start = performance.now();
fibonacciJS(n)
jsTime += (performance.now() - start)
}
console.log(`wasm 模块平均调用时间:${cTime / 10}ms`)
console.log(`js 模块平均调用时间:${jsTime / 10}ms`)
}
}
)
script>
<body>
body>
html>
然后执行 node node.js
开启 http
服务,接着在浏览器中打开 http://localhost:8888/index.html
,控制台中输出如下:
斐波纳切数字: 5,运行 10 次
index.html:34 wasm 模块平均调用时间:0.001499993959441781ms
index.html:35 js 模块平均调用时间:0.005500001134350896ms
index.html:22 斐波纳切数字: 15,运行 10 次
index.html:34 wasm 模块平均调用时间:0.005999993300065398ms
index.html:35 js 模块平均调用时间:0.15650001005269587ms
index.html:22 斐波纳切数字: 25,运行 10 次
index.html:34 wasm 模块平均调用时间:0.6239999900572002ms
index.html:35 js 模块平均调用时间:1.1620000121183693ms
index.html:22 斐波纳切数字: 35,运行 10 次
index.html:34 wasm 模块平均调用时间:70.59700000681914ms
index.html:35 js 模块平均调用时间:126.21099999523722ms
index.html:22 斐波纳切数字: 45,运行 10 次
index.html:34 wasm 模块平均调用时间:8129.7520000021905ms
index.html:35 js 模块平均调用时间:16918.658500007587ms
整理成表格看一下:
可以看到 wasm
很明显的提高了运行速度,运行时间稳定在 js
的一半,当规模达到 45
的时候,wasm
的运行时间比 js
少了整整 8
秒。
这里也可以看出,如果对于计算密集型的应用,wasm
可以大展身手了。
来看一些目前已经成功落地的 WebAssembly
的应用。
eBay
的条形码扫描
eBay
在原生应用中有专门的 C++
库用于条形码的扫描,在 H5
中利用开源 JavaScript
库 BarcodeReader
做了一个带条形码扫描功能的Web版本。 问题是它只有在 20%
的时间表现良好。 剩余的 80%
的时间运行非常缓慢,准确率也不高。
最终的解决方案是通过 wasm
,将原有的 c++
库引入,以及业界十分有名的、基于 C
语言编写的开源条形码扫描库 ZBar
引入,再加上原本的 js
库,三者协助,最终识别率达到了 100%
。
产品上线后的最终效果如下图所示。
产品在上线使用了一段时间后,eBay
技术团队对应用的条形码扫描情况进行了统计,结果发现有 53%
的成功扫描来自于 ZBar
;34%
来自于自研的 C++
库。剩下的 13%
则来自于第三方的 JavaScript
库实现。可见,其中通过 Wasm
实现得到的扫描结果占据了总成功次数的 87%
。
更详细的过程可以参考 WebAssembly在eBay的实践:速度提升50倍。
AutoCAD
AutoCAD
是一款由将近 40
年历史的知名桌面端设计软件,被广泛地用于土木建筑、装饰装潢、工业制图等多个领域中。
最初基于C++
编译为 Java
代码供 Android
设备使用,最后,在 Google Web Toolkit
(一个 Google
开发的可以使用 Java
语言开发 Web
应用的工具集)的帮助下,又将这些 Java
代码转译为了 Web
平台可用的 JavaScript
代码。但最后生成的 Web
应用代码库十分庞大,且在浏览器中的运行性能并不可观。这个「粗糙版」的 Web
应用发布于 2014
年。
2015
年通过 Asm.js
将原有的 C++
代码中的主要功能直接进行编译移植到到 Web
平台,性能有了很大的提告。2018
年 3
月,基于 Wasm
构建的 AutoCAD Web
也成功诞生,https://web.autocad.com/login。
谷歌地球
Google
地球最初使用 C++
语言在 Windows
平台上开发。后来移植到了 Android
和 iOS
平台中。2017
年 4
月 18
日,经过全新设计的 Google
地球 9.0
发布。由于采用了 Native Client 技术,刚发布时仅能在Chrome
中运行。2020
年 2
月 27
日,Google
使用 C++
语言通过 WebAssembly
上重写了 Google
地球,从此 Google
地球可以在 Firefox
和 Edge
上运行。
bilibili 上传视频的封面
在 知乎 看到的一个回答。
投稿视频的时候,当你的视频还在上传中,已经可以自由选择AI推荐的封面。这里采用了webassembly+AI的前端整合。
webassembly 负责读取本地视频,生成图片;
tensorflow.js 负责加载AI训练过的 model,读取图片并打分。
从完全的服务端架构 => 前端架构 && 服务端兜底。
webassembly支持解析99%以上的视频编码格式,速度提升体验惠及约50%的web投稿用户。
作者:Stois Fu
链接:https://www.zhihu.com/question/265700379/answer/951118579
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
由于当前 Wasm
标准下,Wasm
模块不能直接操纵 dom
元素,所以 WebAssembly
主要应用在了一些计算密集型的场景下,视频的解码编码、图像处理、涉及到复杂计算的算法、加密算法等等。
Wasm
除了应用在浏览器中,也可以应用到 out-of-web
环境中。通过 WASI
(WebAssembly System Interface
,Wasm
操作系统接口)标准,Wasm
可以直接与操作系统打交道。通过已经在各种环境实现了 WASI
标准的虚拟机,我们就可以将 wasm
用在嵌入式、IOT 物联网以及甚至云,AI 和区块链等特殊的领域和场景中。
有了 WASI
标准,文章最开始介绍的当前应用的架构在未来可能会发生质的改变。
上边架构的最大问题就是各个操作系统不能兼容,同一个app
需要采用不同的语言在不同平台下各实现一次。
比如一款 A
应用,如果想实现跨平台的话,我们需要用 java
完成在安卓上的开发,用 Objective-C
实现 iOS
上的开发,用 C#
实现 PC
端的开发… …也就是下边的样子。
但如果有了 wasm
,我们只需要选择任意一门语言,然后编译成 wasm
,就可以分发到各个平台上了。
这也是 Wasm
官方宣传的 Ending
定律,Any application that can be compiled to WebAssembly, will be compiled to WebAssembly eventually.
此时回顾一下,WebAssebmly
的定义,应该会清晰很多了。
WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine. Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
它不是一种语言,而是规定了一种虚拟指令集,可以作为各个语言的编译目标,然后通过 wasm
的虚拟机运行到浏览器还有其他各个平台中。
对于前端领域,当前 Webassembly
在某些场景下可以有效提高前端项目的性能,并且可以将 C/C++
领域的一些优秀的库通过编译直接运行到浏览器中。如果前端遇到了性能的问题,不妨可以考虑下 WebAssmbly
的方案。
WebAssembly 现状与实战
WebAssembly: another JVM?
Why WebAssembly is a Big Deal
Awesome WebAssembly Languages
极客时间WebAssembly入门课,很系统,强烈推荐
汇编语言入门教程
WebAssembly在eBay的实践:速度提升50倍