初探 WebAssembly

什么是 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

安装

  1. git clone https://github.com/juj/emsdk.git
  2. win 和 mac 在略有不同
    1. win:emsdk install --global latest
    2. mac:./emsdk install --global latest
  3. win 环境下每次重启终端都要执行一次,windows电脑中只能在cmd、powershell中使用,在git bash无效
    1. emsdk activate latest
    2. ./emsdk activate latest
  4. 如果没有这个脚本,下一步会提醒手动设置全局变量
    1. emsdk_env.bat
    2. source ./emsdk_env.sh
  5. 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…
初探 WebAssembly

更多指令: Emscripten 编译器(emcc) 命令总结

Rust

安装

  1. 安装 rust,上官网,cargo也会一起装好
  2. cargo install wasm-pack:在window平台它是依赖 visual studio 中的一些 sdk 的,所以要先装一下 vs,不然执行这条命令的时候会报错
  3. cargo new <folderName>

使用

  1. 配置 Cargo.toml,下面是编译为 .wasm 最基本的配置
[lib]
crate-type = ["cdylib"]

[dependencies]
wasm-bindgen = "0.2"
  1. 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)
}
  1. wasm-pack build --target web:生成的 .wasm 文件和胶水代码会在 pkg 目录下

AssmeblyScript

安装

  1. npm install -D assemblyscript
  2. 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);
  }
}

性能对比

用斐波那契数列、数据求和、排序算法来做对比的话
初探 WebAssembly

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:

  1. 引用文件时需要本地起一个 server

c2wasm:

  1. 要先装python环境,不然安装不会有反应
  2. mac环境中装了python仍然报错
    1. 初探 WebAssembly
    2. 解决方案:在emsdk.py中添加如下两行代码issue with emsdk on mac
import ssl
ssl._create_default_https_context = ssl._create_unverified_context
  1. -O3 这类优化在发包的时候一定要加上,不然跑起来可能比js还慢

rust2wasm:

  1. 要先装visul studio,不然安装 wasm-pack 的时候会报错没有 link.exe。也可以不安装vs,有兴趣的可以自己查查看
  2. rust的胶水代码可读性比较强,可以作为参考

参考资料

tigercosmos.xyz/post/2020/0…
huningxin.github.io/opencv.js/s…

原文链接:https://juejin.cn/post/7316354375701168166 作者:四霉

(0)
上一篇 2023年12月26日 下午4:22
下一篇 2023年12月26日 下午4:32

相关推荐

发表回复

登录后才能评论