TOC
这个东西其实我大约一年之前听到了解到,但是一直没有尝试。
正巧最近自己写了个文本校对工具,公司内部的一些人员在用,功能上没有问题,主要就是因为需求是全部一行来进行对比,然后复杂度又高达 O(nm),在我们公司的使用情况下,n 基本等于 m 所以复杂度也就是 O(n^2)。简单来说就是一旦 5000 字以上就爆炸(内存直接溢出,超出 v8 引擎大约 1.5G 的限制),虽然这种高字数的对比并不多见,但身为一个有责任感的码农,很想找办法解决这个问题,所以我重新考虑了这个东西,想要用更高性能的语言来进行计算,也进行了一番尝试。
在一众语言中选择了 Rust,因为据说对于WebAssembly
的支持是最健全的,正好我也玩过一点点的 Rust。
提前告知一下结果,可能会让大家失望。我做了一个 demo(模拟 LCS 需要的那个表结构)之后,发现比 JS 还慢,并且内存占用上也并没有特别大的优势。所以我想告诉大家,高性能不是那么好拿的,V8 引擎本身已经很快了,算法上一些复杂度降低不了,语言的那点性能差异可能完全不够看。
当然 这只是我个人的使用场景。
起步
第一部当然是安装 Rust 的整个编译环境
curl https://sh.rustup.rs -sSf | sh // 安装 Rust rustup target add wasm32-unknown-unknown –toolchain nightly // 安装 To wasm 的组件 cargo +nightly install wasm-bindgen-cli
搭建
我这里会用到wasm-bindgen
,他的作用是方便 JS 与 Rust 进行函数调用上的变量交互,否则你获取 Rust 运行的结果还需要读取内存(arrayBuffer) 然后进行编码。特别麻烦,就像下面这样
function getStr(rust, fnKey, lenKey, args) {
const memory = rust.instance.exports.memory
const offset = rust.instance.exports[fnKey](args)
const stringBuffer = new Uint8Array(
memory.buffer,
offset,
rust.instance.exports[lenKey]()
)
let str = ''
for (let i = 0; i < stringBuffer.length; i++) {
str += String.fromCharCode(stringBuffer[i])
}
return str
}
async function main() {
let res = await fetch(
'./target/wasm32-unknown-unknown/debug/rust_diff_core.wasm'
)
const bytes = await res.arrayBuffer()
const rust = await WebAssembly.instantiate(bytes)
}
main()
让我们正式开始
cargo +nightly new js-hello-world –lib 建立一个 project
打开Cargo.toml
修改如下
[package]
name = "hello_world"
version = "0.1.0"
authors = ["The wasm-bindgen Developers"]
edition = "2018"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2.30"
修改src/lib.rs
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
fn log_str(s: String);
}
#[wasm_bindgen]
pub fn say(woo: &str) -> String {
woo.to_string()
}
接下来我们需要一个 build.sh
来帮助我们自动进行从 Rust > wasm > wasm-bindgen 包装 的步骤
cargo +nightly build --target wasm32-unknown-unknown &&
wasm-bindgen ./target/wasm32-unknown-unknown/debug/hello_world.wasm --out-dir .
现在 运行一下 build.sh
我们会发现根目录出现了 hello_world.wasm 以及 hello_world.js。没错,wasm-bindgen 的原理就是建立.js 的包装,方便我们进行通信
创建index.js
const rust = import('./hello_world')
rust.then(m => console.log(m.say('hello'))).catch(console.error)
创建package.json
{
"scripts": {
"build": "webpack",
"serve": "webpack-dev-server"
},
"devDependencies": {
"text-encoding": "^0.7.0",
"html-webpack-plugin": "^3.2.0",
"webpack": "^4.11.1",
"webpack-cli": "^3.1.1",
"webpack-dev-server": "^3.1.0"
}
}
以及 webpack 的设置 建立 webpack.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpack = require('webpack')
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js'
},
plugins: [
new HtmlWebpackPlugin(),
new webpack.ProvidePlugin({
TextDecoder: ['text-encoding', 'TextDecoder'],
TextEncoder: ['text-encoding', 'TextEncoder']
})
],
mode: 'development'
}
安装依赖 开始运行
npm i npm run server
你的控制台应该已经成功输出了一个 hello,实际上这就是最简单的变量交互。你现在可以做更多的事情了。如果修改了 Lib.rs 请重新运行build.sh
总结
Rust真的有点难。