WebAssembly 基础
WebAssembly 介绍
官网介绍: WebAssembly
或者 wasm
是一个可移植、体积小、加载快并且兼容 Web 的全新格式。分析一下 WebAssembly
这个词由 Web
和 Assembly
组成, Web
代表和前端有关, Assembly
汇编的意思,汇编对应着机器码,而机器码对应着指令集,那么什么是指令集呢?先来看一张图片图片来自 WebAssembly入门-未来可能发生的巨变[1] 参考上图,计算机的主要架构如上。最底层是 CPU
的指令集,主要分为复杂指令集和简单指令集。复杂指令集是 x86
、 x64(也叫 x86-64, amd64)
两种架构,专利在 Intel
和 AMD
两家公司手里, 该架构 CPU
主要是 Intel
和 AMD
两家公司,这种 CPU
常用在 PC
机上,包括 Windows
, macOS
和 Linux
。简单指令集是 arm
一种架构,专利在 ARM
公司手里,该架构 CPU
主要有高通、三星、苹果、华为海思、联发科等公司。这种 CPU
常用在手机上,包括安卓和苹果。那么什么是指令集呢?直接把阮一峰的老师的一个 例子[2] 粘过来,大家可以看一下。 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入门-未来可能发生的巨变[3] )
简单来说就是,编译器将 C++
, Go
, Rust
等编译为中间代码,再转化为 WebAssembly
字节码(类似java的字节码), WebAssembly
字节码是一种抹平了不同 CPU
架构的机器码, WebAssembly
字节码不能直接在任何一种 CPU
架构上运行, 但由于非常接近机器码,可以非常快的被翻译为对应架构的机器码,因此 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(/[\s\r\n]+/g).map(str => parseInt(str, 16))
)).then(module => {
const instance = new WebAssembly.Instance(module)
const { add, square } = instance.exports
console.log('2 + 4 =', add(2, 4))
console.log('3^2 =', square(3))
console.log('(2 + 5)^2 =', square(add(2 + 5)))})
将上面代码拷贝到浏览器控制台就能看到结果。综上所述可以知道wasm其实是一种二进制字节码,和机器码比较类似,我们都知道汇编语言可以被转化为机器码,同样wasm也有类似的汇编语言,被称为 S-表达式
( mdn定义[4] ),可以使用工具 wat2wasm
在 S-表达式
和 wasm
字节码之间相互转换
WebAssembly 由来
由于前端业务场景越来越多,业务越来越复杂,导致在一些性能比较差的机器上表现不好。表现不好一方面确实是业务场景比较复杂,另一方面就是 javascript
这门语言本身的缺陷。其一 javascript
是一门解释型语言,首先解释一下编译型和解释型编程语言的区别。编译型:将源代码经过编译器转为机器码,在执行 解释型:代码运行过程中,将源代码经过编译器生成中间代码,中间代码再通过虚拟器生成目标平台的机器码执行。引用知乎上的一句话,编译型就是提前做好一桌子菜,再吃,而解释型就相当于吃火锅,一边吃,一边煮 所以说解释型编译语言会比编译型语言慢些。个人认为这个并不怎么影响性能。其二 javascript
是一门动态语言编写程序时无需考虑变量类型,对于开发人员确实友好,但是对于js引擎来说并不友好。在js引擎没有引入JIT之前,假如一个方法被不同模块调用10次,那么这个方法也会被解释10次,显然这是很浪费性能。引入JIT后看一下v8引擎执行js的过程Js 源代码经过词法分析、语法分析、语义分析生成AST树,再生成中间代码,中间代码进入解释器中执行,同时分析器会监控代码的执行情况,如果一段代码被反复执行,那么这个段代码被标记为热点代码,同时会把这段代码送到编译器中编译,同时生成优化后机器码,等下次执行到这段代码,就可以直接运行优化后的代码。但是也有特殊的情况,因为js是动态语言,类型是不固定的,有可能上一秒这个段代码被标记为热点代码,下一秒该热点代码对应的优化代码可能就被丢弃。感兴趣的可以看一下 这篇文章[5]
ASM.js
所以为了解决类型问题 WebAssembly
前身 ASM.js
出现, ASM.js
是 Mozilla
在 2013
年推出的, ASM.js
是一个 javascript
严格子集, ASM.js
虽然可以手写,但是一般都是使用编译器将 C++/C
或者一些其他语言转为 ASM.js
文件。生成的 ASM.js
文件变量都是静态类型,不用在运行时判断变量类型,从而使得 js
引擎可以采用 AOT(Ahead Of Time)
的编译策略,也就是在运行前直接编译成机器码,因此运行速度会有一定的提升。那为什么 ASM.js
并没有流行起来,其一它还是没有一个统一的标准。它只是一个由一个厂商推出的,非标准的 JavaScript 子集而已,而它的使用者根据自己的偏好和习惯来使用它。WebAssembly 则不同,它是由几大主要的浏览器厂商共同设计的。其二无论 ASM.js
如何优化,归根结底还是一个js文件,看一下上面v8引擎执行js的图片,可以看出只要是js文件,就需要经过解析,生成中间代码,而这两步是JavaScript代码在引擎执行过程当中消耗时间最多的两步。而WebAssembly不用经过这两步。这就是WebAssembly比asm.js更快的原因。
Emscripten介绍
Emscripten 是一个针对WebAssembly的开源编译器,它可以将C和C++代码或任何其他使用LLVM的语言编译成WebAssembly,并在Web、Node.js或其他wasm运行时运行它。Emscripten 中包含俩个重要的工具链
emcc 是clang和gcc的临时替代品,emcc使用LLVM和clang将c/c++编译为WebAssembly
EmscriptenSDK用于安装整个工具链,包括EMCC和LLVM等。EmscriptenSDK(Emsdk)可以在Linux、Windows或MacOS上使用。
安装
# Get the emsdk repo
git clone [https://github.com/emscripten-core/emsdk.git](https://github.com/emscripten-core/emsdk.git "https://github.com/emscripten-core/emsdk.git")
# Enter that directory
cd emsdk
# Fetch the latest version of the emsdk (not needed the first time you clone)
git pull
# Download and install the latest SDK tools.
./emsdk install latest
# Make the "latest" SDK "active" for the current user. (writes .emscripten file)
./emsdk activate latest
# Activate PATH and other environment variables in the current terminal
source ./emsdk_env.sh
通过以上操作,在当前命令行运行 emcc -v
,打印如下代表安装成功
emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 2.0.29 (28ca7fb7ce895b21013212e4644a5794a15a76f9)
clang version 14.0.0 ([https://github.com/llvm/llvm-project](https://github.com/llvm/llvm-project "https://github.com/llvm/llvm-project") 8e284be04f2cd43a821289133a759afa2844f935)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/bytedance/Desktop/emsdk/upstream/bin
为了确保每次打开终端都可以使用emcc,还需要将如下代码加入到 .bash_profile
或 .zsh
source emcc目录/emsdk_env.sh &> /dev/null
使用
将 c/c++
编译为js
// test.cpp
#include
using namespace std;
int main()
{
cout << "Hello World" << endl;
}
运行 emcc ./test.cpp
,同级目录生成 a.out.js
和 a.out.wasm
文件,第二个是包含编译代码的WebAssembly文件,第一个是包含加载和执行代码的运行时支持的JavaScript文件。然后可以使用 nodejs
运行 js
node ./a.out.js
终端输出 Hello World
Emscripten还可以生成用于测试嵌入式JavaScript的HTML。要生成HTML,请使用-o(输出)命令并指定一个html文件作为目标文件:
emcc test.cpp -o hello.html
然后可以在浏览器中打开hello.html 不幸的是,一些浏览器(包括Chrome、Safari和Internet Explorer)不支持file://xhr请求,并且不能加载HTML所需的额外文件(如.wasm文件或下面提到的打包文件数据)。对于这些浏览器,您需要使用本地Web服务器提供文件,然后打开 http://localhost:8000/hello.html[6] ). 本人在练习的时候使用的是 http-server
安装
npm i http-server -g
在工作目录运行
# -o 打开浏览器
# -p 指定端口
http-server ./ -o -p 8085
在浏览器中打开 8085
端口
WebAssembly 会取代 Javascript吗?
我认为不会。我认为 WebAssembly
和 JavaScript
是相辅相成的,WebAssembly是被设计成JavaScript的一个完善、补充,而不是一个替代品。大家怎么认为?
参考链接
Emscripten 官网[7] c++项目转成wasm全过程[8] https://juejin.cn/post/6844903709806182413#heading-27[9] https://juejin.cn/post/6844903469808091143[10]
WebAssembly入门-未来可能发生的巨变: https://windliang.wang/2020/11/18/WebAssembly%E5%85%A5%E9%97%A8-%E6%9C%AA%E6%9D%A5%E5%8F%AF%E8%83%BD%E5%8F%91%E7%94%9F%E7%9A%84%E5%B7%A8%E5%8F%98/
[2]例子: http://www.ruanyifeng.com/blog/2018/01/assembly-language-primer.html
[3]WebAssembly入门-未来可能发生的巨变: https://windliang.wang/2020/11/18/WebAssembly%E5%85%A5%E9%97%A8-%E6%9C%AA%E6%9D%A5%E5%8F%AF%E8%83%BD%E5%8F%91%E7%94%9F%E7%9A%84%E5%B7%A8%E5%8F%98/
[4]mdn定义: https://developer.mozilla.org/zh-CN/docs/WebAssembly/Understanding_the_text_format
[5]这篇文章: https://juejin.cn/post/6844903469262831624
[6]http://localhost:8000/hello.html: http://localhost:8000/hello.html
[7]Emscripten 官网: https://emscripten.org/index.html
[8]c++项目转成wasm全过程: https://zhuanlan.zhihu.com/p/158586853
[9]https://juejin.cn/post/6844903709806182413#heading-27: https://juejin.cn/post/6844903709806182413#heading-27
[10]https://juejin.cn/post/6844903469808091143: https://juejin.cn/post/6844903469808091143
往期推荐
最后
欢迎加我微信,拉你进技术群,长期交流学习...
欢迎关注「前端Q」,认真学前端,做个专业的技术人...