什么是 WebAssembly?
- 一种新型的代码,可以运行在 Web 浏览器,提供一些新特性并主要专注于高性能
- 主要不是用于写,而是 C/C++、C#、Rust 等语言编译的目标,所以你即使不知道如何编写 WebAssembly 代码也能利用它的优势
- 其他语言编写的代码也能以近似于原生速度运行,客户端 App 也能在 Web 上运行
- 在浏览器或 Node.js 中可以导入 WebAssembly 模块,JS 框架能够使用 WebAssembly 来获得巨大的性能优势和新的特性的同时在功能上易于使用
优势
- 高效、便利:通过利用一些通用的硬件能力,能够跨平台以近乎于原生的速度执行
- 可读、可调试:WebAssembly 是一种低层次的汇编语言,但是它也有一种人类可读的文本格式,使得人们可编写代码、查看代码、可调试代码。
- 确保安全:WebAssembly 明确运行在安全、沙箱的执行环境,类似其他 Web 的代码,它会强制开启同源和一些权限策略。
- 不破坏现有的 Web:WebAssembly 被设计与其他 Web 技术兼容运行,并且保持向后兼容性。
编码平台 WebAssembly 给 Web 平台添加了两块内容:一种二进制格式代码,以及一系列可用于加载和执行二进制代码的 API。 WebAssembly 目前处于一个萌芽的节点,之后肯定会涌现出很多工具,而目前有四个主要的入口:
- 在汇编层面直接编写和生成 WebAssembly 代码(.wat -> .wasm)
- 使用 EMScripten 来移植 C/C++ 应用
- 编写 Rust 应用(使用wasm-pack),然后将 WebAssembly 作为它的输出
- 使用 AssemblyScript,它是一门类似 TypeScript 的语言,能够编译成 WebAssembly
Js 中使用 .wasm 文件
浏览器不能直接用.wasm
格式的文件中的函数和数据,需要拿 js 先写一些胶水代码。(通常能将高级语言编译为.wasm
的框架都有生成胶水代码的能力)
使用逻辑:.wasm文件 -> 胶水代码 -> 业务代码
C & Cpp
安装
git clone https://github.com/juj/emsdk.git
- win 和 mac 在略有不同
- win:
emsdk install --global latest
- mac:
./emsdk install --global latest
- win:
- win 环境下每次重启终端都要执行一次,windows电脑中只能在cmd、powershell中使用,在git bash无效
emsdk activate latest
./emsdk activate latest
- 如果没有这个脚本,下一步会提醒手动设置全局变量
emsdk_env.bat
source ./emsdk_env.sh
emcc -v
:如果有log,说明安装成功
使用
#include <stdio.h>
#include <emscripten/emscripten.h> // 需要引入这个包来导出函数
EMSCRIPTEN_KEEPALIVE
int fib(int n) {
if (n < 2) {
return n;
} else {
return fib(n - 1) + fib(n - 2);
}
}
EMSCRIPTEN_KEEPALIVE
int main(int argc, char ** argv) {
printf("fib(40) = %d\n", fib(40));
}
简单应用的话在cmd emcc <fileNmae>.c
用于生成 .wasm
文件,同时也会生成一个对应的 .js
,即胶水代码,可以通过这个文件直接使用 .wasm
中封装好的方法。
emcc fib.c -o fib.wasm --no-entry -O3
:自己写胶水代码的话可以直接用这个,main函数运行时的依赖参数就可以不用传入。其中 -O3
最好加上(或者更高程度的优化指令),不然跑起来可能比 js 的代码还慢
stackoverflow.com/questions/5…
更多指令: Emscripten 编译器(emcc) 命令总结
Rust
安装
- 安装 rust,上官网,cargo也会一起装好
cargo install wasm-pack
:在window平台它是依赖 visual studio 中的一些 sdk 的,所以要先装一下 vs,不然执行这条命令的时候会报错cargo new <folderName>
使用
- 配置
Cargo.toml
,下面是编译为.wasm
最基本的配置
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
- demo文件
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fib(n: i32) -> i32 {
if n <= 1 {
return n;
}
fib(n - 1) + fib(n - 2)
}
wasm-pack build --target web
:生成的.wasm
文件和胶水代码会在 pkg 目录下
AssmeblyScript
安装
npm install -D assemblyscript
npx asinit .
:生成脚手架,然后跟着脚手架走就行
使用
AssemblyScript 可以理解为 Typescript 的子集,数据类型很少类型 | AssemblyScript 中文网 写起来就是ts,编译之后为wasm,不过一年了才从 0.26 -> 0.27,所以可以理解为半死不活的状态,不推荐使用。
export function fib(num: i32): i32 {
if (num < 2) {
return num;
} else {
return fib(num - 1) + fib(num - 2);
}
}
性能对比
用斐波那契数列、数据求和、排序算法来做对比的话
const asObj = await WebAssembly.instantiateStreaming(
fetch("../as-demo/build/release.wasm"),
{}
);
const cObj = await WebAssembly.instantiateStreaming(
fetch("../cpp-demo/demos/fib.wasm"),
{}
);
const rustObj = await WebAssembly.instantiateStreaming(
fetch("../rust-demo/pkg/rust_demo_bg.wasm"),
{}
);
const jsFib = (n) => (n < 2 ? n : jsFib(n - 1) + jsFib(n - 2));
const { fib: asFib } = asObj.instance.exports;
const { fib: cFib } = cObj.instance.exports;
const { fib: rustFib } = rustObj.instance.exports;
const depth = 30;
timeLog(() => cFib(depth), "c");
timeLog(() => rustFib(depth), "rust");
timeLog(() => asFib(depth), "as");
timeLog(() => jsFib(depth), "js");
import { timeLog } from "../utils/index.js";
const rustObj = await WebAssembly.instantiateStreaming(
fetch("../../rust-demo/pkg/rust_demo_bg.wasm"),
{}
);
const { sum: wasmSum, memory, malloc, free } = rustObj.instance.exports;
const arr = new Int32Array(1e8).fill(1);
// 分配足够的内存
const ptr = malloc(arr.length * arr.BYTES_PER_ELEMENT);
// 创建一个新的 Uint8Array,它共享 WebAssembly 的内存
const wasmMemory = new Uint8Array(memory.buffer);
// 将 arr 的内容复制到分配的内存中
wasmMemory.set(new Uint8Array(arr.buffer), ptr);
const jsSum = (arr) => arr.reduce((acc, cur) => acc + cur, 0);
timeLog(() => wasmSum(ptr, arr.length), "rust");
timeLog(() => jsSum(arr), "js");
// 释放内存
free(ptr, arr.length * arr.BYTES_PER_ELEMENT);
import { timeLog } from "../utils/index.js";
const rustObj = await WebAssembly.instantiateStreaming(
fetch("../../rust-demo/pkg/rust_demo_bg.wasm"),
{}
);
const { sort: wasmSort, memory, malloc, free } = rustObj.instance.exports;
const arr = new Int32Array(1e6)
.fill(1)
.map(() => Math.floor(Math.random() * 1e6));
// 分配足够的内存
const ptr = malloc(arr.length * arr.BYTES_PER_ELEMENT);
// 创建一个新的 Uint8Array,它共享 WebAssembly 的内存
const wasmMemory = new Uint8Array(memory.buffer);
// 将 arr 的内容复制到分配的内存中
wasmMemory.set(new Uint8Array(arr.buffer), ptr);
// 使用 WebAssembly 函数对数组进行排序
let sortedArr;
timeLog(() => {
wasmSort(ptr, arr.length);
// 创建一个新的 Int32Array,它共享 WebAssembly 的内存
const wasmMemoryAfterSort = new Int32Array(memory.buffer);
sortedArr = wasmMemoryAfterSort.subarray(
ptr / arr.BYTES_PER_ELEMENT,
ptr / arr.BYTES_PER_ELEMENT + arr.length
);
return sortedArr;
}, "rust");
timeLog(() => arr.sort((a, b) => a - b), "js");
// 释放内存
free(ptr, arr.length * arr.BYTES_PER_ELEMENT);
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn fib(n: i32) -> i32 {
if n <= 1 {
return n;
}
fib(n - 1) + fib(n - 2)
}
#[wasm_bindgen]
pub fn sum(arr: *const i32, len: usize) -> i32 {
let arr: &[i32] = unsafe {
assert!(!arr.is_null());
std::slice::from_raw_parts(arr, len)
};
arr.iter().sum()
}
#[wasm_bindgen]
pub fn sort(ptr: *mut i32, length: usize) {
let slice: &mut [i32] = unsafe { std::slice::from_raw_parts_mut(ptr, length) };
slice.sort();
}
#[wasm_bindgen]
pub fn malloc(size: usize) -> *mut u8 {
let mut buf: Vec<u8> = Vec::with_capacity(size);
let ptr: *mut u8 = buf.as_mut_ptr();
std::mem::forget(buf);
return ptr;
}
#[wasm_bindgen]
pub fn free(ptr: *mut u8, size: usize) {
unsafe {
let _buf: Vec<u8> = Vec::from_raw_parts(ptr, 0, size);
}
}
踩坑记录
common:
- 引用文件时需要本地起一个 server
c2wasm:
- 要先装python环境,不然安装不会有反应
- mac环境中装了python仍然报错
- 解决方案:在
emsdk.py
中添加如下两行代码issue with emsdk on mac
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
- -O3 这类优化在发包的时候一定要加上,不然跑起来可能比js还慢
rust2wasm:
- 要先装visul studio,不然安装
wasm-pack
的时候会报错没有link.exe
。也可以不安装vs,有兴趣的可以自己查查看 - rust的胶水代码可读性比较强,可以作为参考
参考资料
tigercosmos.xyz/post/2020/0… huningxin.github.io/opencv.js/s…