基本概念
WebAssembly 是一种新的编码方式,可以在现代浏览器中运行。
-
它是一种低级的类汇编语言
-
具有紧凑的二进制格式
-
可以接近原生的性能运行
-
并为 C/C++、C#、Rust 等语言提供一个编译目标,以便它们可以在 Web 上运行
-
它也被设计为可以与 JavaScript 共存,允许两者一起工作
理解机器码、汇编语言与 WebAssembly
机器码是计算机可以直接执行的语言,而汇编语言比较接近机器码,不过也需要转换成机器码之后计算机才可以执行。
不同的 CPU 架构(ARM、x64、x86)需要不同的机器码和汇编,高级语言(C/C++、C# 等)可以编译成机器码,以便在 CPU 上运行。
而 WebAssembly 其实并不是汇编语言,它不只对特定的机器,而是针对浏览器的。WebAssembly 是中间编译器目标。
文件格式
-
文本格式:.wat (TEXTUAL FORMAT)
-
二进制格式:.wasm (BINARY FORMAT)
WebAssembly 能做什么
-
可以把你编写的 C/C++、C#、Rust 等语言的代码编译成 WebAssembly 模块
-
你可以在 Web 应用中加载该模块,并通过 JavaScript 调用它
-
它并不是为了替代 JS,而是与 JS 一起工作
-
仍然需要 HTML 和 JS,因为 WebAssembly 无法访问平台 API,例如 DOM、WebGL 等
WebAssembly 如何工作
以 C/C++ 为例子:
hello.c -> EMSCRIPTEN -> hello.wasm
C/C++ 代码通过 EMSCRIPTEN 编译器编译为 .wasm 文件,然后配合 js 和 HTML 文件一起在浏览器中工作。
WebAssembly 优点
-
快速、高效、可移植:通过利用常见的硬件能力,WebAssembly 代码在不同平台上能够以接近本地速度运行
-
可读、可调式:WebAssembly 是一门低阶语言,但是它却有一种人类可读的文本格式(其标准最终版本目前仍在编制),这允许通过手工来写代码,看代码以及调试代码
-
保持安全:WebAssembly 被限制运行在一个安全的沙箱执行环境中。像其它网络代码一样,它遵循浏览器的同源策略和授权策略
-
不破坏网络:WebAssembly 的设计原则是与其它网络技术和谐共处并保持向后兼容
环境搭建
官方文档:
Introduction - Rust and WebAssembly
Step1 安装 wasm-pack
下载地址 wasm-pack
按照官方指引执行:
curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
执行后就直接安装了,如果网络不好可能会安装失败,多试几次,成功之后会有类似下面的提示:
info: downloading wasm-pack
info: successfully installed wasm-pack to `/Users/eagleye/.cargo/bin/wasm-pack`
Step2 安装 cargo-generate
cargo install cargo-generate
这个也可能会遇到网络问题,多试几次就好。
Step3 安装 npm
安装 npm
其实就是安装 NodeJS
:
官网地址:Node.js (nodejs.org)
下载安装好之后,验证一下是否安装好,执行:
npm --version
创建 Hello, World 程序
Step1 WebAssembly 程序准备
克隆官方的模板工程:
cargo generate --git https://github.com/rustwasm/wasm-pack-template
如果网不好可能也会出现失败,多试几次,如果克隆成功会提示输入一个项目名称,官方给的是 wasm-game-of-life
。
完成之后进入到这个目录中:
cd wasm-game-of-life
目录结构如下图所示:
里面有个 Cargo.toml
文件,其中 wasm-bindgen
是一个比较关键的依赖,就是依赖它与 JavaScript 接口的。
再看 src/lib.rs
文件:
mod utils;
use wasm_bindgen::prelude::*;
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
#[cfg(feature = "wee_alloc")]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen]
extern {
fn alert(s: &str); // 此处就是 js 的 window.alert 函数的声明
// 声明了才能在 rust 中使用
}
#[wasm_bindgen]
pub fn greet() { // greet 函数加了 #[wasm_bindgen],所以它是能被前端调用的
// 此处是 rust 代码,调用的就是上面声明的 alert
alert("Hello, wasm-game-of-life!");
}
如果没有 wasm32-unknown-unknown
那么需要先进行手动安装才能编译项目:
rustup target add wasm32-unknown-unknown
编译项目(这里有个坑如果是新装的 wasm32-unknown-unknown 记得重启一下命令窗):
wasm-pack build
第一次编译可能比较慢,因为需要下载 wasm_bindgen
依赖。
编好之后的文件都在 pkg
文件夹中。
Step2 前端程序准备
快速地生成一个套前端代码:
npm init wasm-app www
看到这个输出就是生成好了 🦀 Rust + 🕸 Wasm = ❤
这样就会生成一个 www
文件夹,这里面就是我们的前端代码。
目录结构如下所示:
然后,配置前端程序的依赖项去依赖我们打包出来的 WebAssembly 的程序。
配置 www/package.json
,追加依赖项配置:
"dependencies": {
"wasm-game-of-life": "file:../pkg"
}
wasm-game-of-life
这个名字要与 pkg/package.json
中的 name
保持一致。
最后切到 www
目录下安装依赖:
cd www
npm install
Step3 修改前端代码
www/index.js
import * as wasm from "wasm-game-of-life"; // 引用我们自己编的 wasm 项目
wasm.greet(); // 调用 greet 函数
运行:
npm start
访问:http://localhost:8080
访问效果如下图所示:
性能对比
我将用三种方式实现一个爬楼梯算法,当然用的是最耗时的一种算法,最朴素的递归计算。题目原文: 70. 爬楼梯 - 力扣(LeetCode) (leetcode-cn.com)。
以下结果都是在同一台电脑上运行得出的数据,不同的系统,不同的电脑测试数据肯定也会不一样,所以数据只作为参考。
JavaScript 实现
const climbStairs = function (n) {
if (n <= 2) {
return n;
}
return climbStairs(n - 1) + climbStairs(n - 2);
};
console.time();
console.log(climbStairs(42));
console.timeEnd();
测试几轮下来,耗时基本在 2600 ~ 3050 ms 左右。
WebAssembly 实现
#[wasm_bindgen]
pub fn climb_stairs(n: i32) -> i32 {
if n <= 2 {
return n
}
return climb_stairs(n - 1) + climb_stairs(n - 2);
}
import * as wasm from "wasm-game-of-life";
console.time();
console.log(wasm.climb_stairs(42));
console.timeEnd()
测试几轮下来,耗时基本在 1600 ~ 1900 ms 左右。
Rust 实现
use std::time::Instant;
fn main() {
let now = Instant::now();
climb_stairs(42);
let duration = now.elapsed();
println!("{}", duration.as_micros());
}
pub fn climb_stairs(n: i32) -> i32 {
if n <= 2 {
return n
}
return climb_stairs(n - 1) + climb_stairs(n - 2);
}
测试几轮下来,直接 cargo run
运行耗时基本在 2100 ~ 2400 ms 左右。但是如果 cargo build --release
编译之后运行,运行耗时基本在 700 ~ 800 ms 左右。
综上所述,WebAssembly 的性能肯定是不如原生 rust 的,但是比 JavaScript 还是要好很多的。