阅读 456
WebAssembly 基础

WebAssembly 基础

WebAssembly 介绍

官网介绍: WebAssembly 或者 wasm 是一个可移植、体积小、加载快并且兼容 Web 的全新格式。 分析一下 WebAssembly 这个词由 WebAssembly 组成, Web 代表和前端有关, Assembly 汇编的意思,汇编对应着机器码,而机器码对应着指令集,那么什么是指令集呢?先来看一张图片 图片来自 WebAssembly入门-未来可能发生的巨变 参考上图,计算机的主要架构如上。最底层是 CPU 的指令集,主要分为复杂指令集和简单指令集。 复杂指令集是 x86x64(也叫 x86-64, amd64) 两种架构,专利在 IntelAMD 两家公司手里, 该架构 CPU 主要是 IntelAMD 两家公司,这种 CPU 常用在 PC 机上,包括 WindowsmacOSLinux 。 简单指令集是 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
复制代码

这里的 pushmov 每一条指令就是指令集规定的内容,规定了操作码、操作数以及具体的功能。当然这里是用汇编表示的,主要是为了我们人类来读写,最终还会转成 0,1 序列。上边每个单词都会有一个数字相对应,比如 add 指令对应 00000011 。 通过规定的指令集(加法的指令,压栈指令等),编写相关程序,然后 CPU 就会一条一条的执行,最终实现相应的功能。 而 WebAssembly 就规定了一套指令集,更准确的来说是虚拟指令集,因为这套指令集是跑在虚拟机上的,而不是直接由硬件运行。(指令内容来源自 WebAssembly入门-未来可能发生的巨变

简单来说就是,编译器将 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定义 ),可以使用工具 wat2wasmS-表达式wasm 字节码之间相互转换

WebAssembly 由来

由于前端业务场景越来越多,业务越来越复杂,导致在一些性能比较差的机器上表现不好。表现不好一方面确实是业务场景比较复杂,另一方面就是 javascript 这门语言本身的缺陷。其一 javascript 是一门解释型语言,首先解释一下编译型和解释型编程语言的区别。 编译型: 将源代码经过编译器转为机器码,在执行 解释型:代码运行过程中,将源代码经过编译器生成中间代码,中间代码再通过虚拟器生成目标平台的机器码执行。 引用知乎上的一句话,编译型就是提前做好一桌子菜,再吃,而解释型就相当于吃火锅,一边吃,一边煮 所以说解释型编译语言会比编译型语言慢些。个人认为这个并不怎么影响性能。 其二 javascript 是一门动态语言编写程序时无需考虑变量类型,对于开发人员确实友好,但是对于js引擎来说并不友好。在js引擎没有引入JIT之前,假如一个方法被不同模块调用10次,那么这个方法也会被解释10次,显然这是很浪费性能。引入JIT后看一下v8引擎执行js的过程 Js 源代码经过词法分析、语法分析、语义分析生成AST树,再生成中间代码,中间代码进入解释器中执行,同时分析器会监控代码的执行情况,如果一段代码被反复执行,那么这个段代码被标记为热点代码,同时会把这段代码送到编译器中编译,同时生成优化后机器码,等下次执行到这段代码,就可以直接运行优化后的代码。但是也有特殊的情况,因为js是动态语言,类型是不固定的,有可能上一秒这个段代码被标记为热点代码,下一秒该热点代码对应的优化代码可能就被丢弃。感兴趣的可以看一下 这篇文章

ASM.js

所以为了解决类型问题 WebAssembly 前身 ASM.js 出现, ASM.jsMozilla2013 年推出的, 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)

# 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) 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 <iostream>

using namespace std;

int main()
{
    cout << "Hello World" << endl;
}
复制代码

运行 emcc ./test.cpp ,同级目录生成 a.out.jsa.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 ). 本人在练习的时候使用的是 http-server 安装

npm i http-server -g
复制代码

在工作目录运行

# -o 打开浏览器
# -p 指定端口
http-server ./ -o -p 8085 
复制代码

在浏览器中打开 8085 端口

WebAssembly 会取代 Javascript吗?

我认为不会。我认为 WebAssemblyJavaScript 是相辅相成的,WebAssembly是被设计成JavaScript的一个完善、补充,而不是一个替代品。大家怎么认为?

参考链接

Emscripten 官网 c++项目转成wasm全过程 juejin.cn/post/684490… juejin.cn/post/684490…

文章分类
前端