前言
- 目标:通过实战,理解 WebAssembly 的开发流程,对性能有个简单对比
- 示例:实现leetcode - 斐波那契数列
- 学习准备:需要 0 基础,基于 Mac 环境, 因此 bash 命令行的部分 Windows 的同学可能需要替换下
- 学习时长:约 30 分钟
- 打包工具: webpack 5
- 写作时间:2021-10-13
- 代码@Github:dive-into-wasm
1. 环境安装
安装 Rust 和对应的包。
1.1 安装 Rust
curl -sSf https://static.rust-lang.org/rustup.sh | sh
1.2 更换为国内源
更换为国内源,否则安装太慢了。
新建文件:~/.cargo/config
,内容替换为如下,replace-with
这行可自己 ping
文件中各个国内源头,看哪个源快用哪个:
[source.crates-io]
registry = "https://github.com/rust-lang/crates.io-index"
# 替换成你偏好的镜像源
replace-with = 'sjtu'
# 清华大学
[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"
# 中国科学技术大学
[source.ustc]
registry = "git://mirrors.ustc.edu.cn/crates.io-index"
# 上海交通大学
[source.sjtu]
registry = "https://mirrors.sjtug.sjtu.edu.cn/git/crates.io-index"
# rustcc社区
[source.rustcc]
registry = "git://crates.rustcc.cn/crates.io-index"
1.3 安装 cargo-generate
脚手架
cargo install cargo-generate
备注:如果报错
error: could not find system library 'openssl' required by the 'openssl-sys' crate`
需要先安装 brew install openssl
即可。
1.4 安装 wasm-pack
wasm-pack
把 Rust 编译为 WebAssembly 。
$ cargo install wasm-pack
2. 完成一个 Rust lib 项目
2.1 创建 Rust 项目
$ cargo new rust --lib
这里 rust
是项目的名称,你可以换成你想要的任意名称。
注意:会自动生成名为 rust
的文件夹,不要手动创建这个文件夹。
2.2 配置 Cargo.toml
新增 [lib]
, 在 [dependencies]
下增加 wasm-bindgen
依赖,修改后的 完整内容类似如下:
[package]
name = "rust"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen="0.2"
2.3 lib.rs
代码实现
代码细节此处不讲了,文件内容如下:
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fib(i: u32) -> u32 {
match i {
0 => 0,
1 => 1,
_ => fib(i - 1) + fib(i - 2)
}
}
#[wasm_bindgen]
pub fn fib_tail_call_optimized(i: u32, prev: u32, next: u32) -> u32 {
match i {
0 => next,
1 => next,
_ => fib_tail_call_optimized(i - 1, next, prev + next)
}
}
2.4 编译为 WebAssembly 二进制
在 rust 项目根目录,用 wasm-pack
工具编译:
$ wasm-pack build
成功的话会输出类似如下内容:
...
[INFO]: 📦 Your wasm pkg is ready to publish at .../dive-into-wasm/rust/pkg.
此时在 pkg 目录会生成如下5个文件:rust.js
,rust.d.ts
, rust_bg.js
,rust_bg.wasm
,rust_bg.wasm.d.ts
。rust_bg.wasm
就是二进制文件,我们待会要用到的就是这个了。
3. 完成一个前端项目
完成一个前端项目,并编译到浏览器执行生成的 WebAssembly 。
3.1 创建前端项目
在 rust
目录的父目录执行(即 web
目录和 rust
目录是平级的,否则本教程的示例代码中的路径要调整了):
$ mkdir web && cd web
$ npm init -y
3.2 配置 webpack 和开发服务器
安装依赖:
$ npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin
Webpack 5 默认不开启 WebAssembly 支持,需要手动配置一下,同时用 html-webpack-plugin
自动生成一个入口 HTML ,webpack.config.js
内容如下:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
plugins: [
new HtmlWebpackPlugin({title: '实战 WASM Rust'})
],
// 实验特性
// BREAKING CHANGE: Since webpack 5 WebAssembly is not enabled by default and flagged as experimental feature.
// You need to enable one of the WebAssembly experiments via 'experiments.asyncWebAssembly: true' (based on async modules) or 'experiments.syncWebAssembly: true' (like webpack 4, deprecated).
experiments: {
asyncWebAssembly: true
}
}
3.3 编写代用和测试代码
实现调用 WebAssembly 并增加 JS 实现 ,进行 benchmark 。
JS实现2个方法,一个是普通实现,一个是尾递归优化的实现,src/fib.js
代码:
function fib(i) {
if (i <= 1) return i
return fib(i - 1) + fib(i - 2)
}
function fibTailCallOptimized(i, prev = 0, next = 1) {
if (i <= 1) return next
return fibTailCallOptimized(i - 1, next, prev + next)
}
export {
fib,
fibTailCallOptimized
}
调用和执行 benchmark ,src/index.js
代码:
// 直接引用 wasm 文件
import {fib as wasm_fib, fib_tail_call_optimized as wasm_fib_tail_call_optimized } from '../../rust/pkg/rust_bg.wasm'
import { fib, fibTailCallOptimized } from './fib.js'
function time(timerName, func) {
console.time(timerName)
console.log(`${timerName}: `, func())
console.timeEnd(timerName)
}
// js 实现数字不能太大,否则 CPU 占满,运行不出来结果
const num = 30
time('wasm_fib', () => wasm_fib(num))
time('wasm_fib_tail_call_optimized', () => wasm_fib_tail_call_optimized(num, 0, 1))
time('fib', () => fib(num))
time('fibTailCallOptimized', () => fibTailCallOptimized(num))
注意第一行引用了 rust_bg.wasm
二进制代码。
注意: 浏览器安全策略禁止通过 file://
协议加载 wasm
文件,所以我们这里使用的 webpack-dev-server
。
3.4 浏览器运行及benchmark
webpack 打包并在浏览器运行测试效果。
$ npx webpack serve
在浏览器打开,通常是 http://localhost:8080/
。控制台输入类似如下(已删除结果的展示,你可以自己检查下,首先保证运行结果的一致性):
wasm_fib: 0.193115234375 ms
wasm_fib_tail_call_optimized: ms
fib: 0.93701171875 ms
fibTailCallOptimized: 0.16796875 ms
可以看到 wasm
执行效率比 JS 未做优化的高 5 倍左右;
尾调用优化后是 2 倍左右。