最近,看了一篇文章,介绍Node.js可以借助WebAssembly大幅度提高性能。好奇心起,于是便想玩一下。
众所周知,Node.js用来开发轻量应用十分方便,但在高并发或者计算较密集(例如:加密/解密、图形运算)的场景下性能比不上C、Rust、Go这些语言都弱很多。如果你的选择时“既要方便又要性能强悍”,那么完全可以来做一个hybird应用。用Node.js做应用的容器,把Rust等开发编译的wasm装载进来运行,似乎问题就解决。
WebAssembly是什么?
WebAssembly (wasm) 是一种具有广泛规范的简单机器模型和可执行格式。它被设计为可移植、紧凑并以本机速度或接近本机速度执行。包括 Rust、C、Java、Python、Ruby、.NET 在内等诸多语言都可通过 wasm 执行。此外,wasm 也可运行在各种硬件和操作系统之上,在浏览器运行也没有问题。
简单理解:就是一个可以跨平台跨语言调用的dll库。
Deno又是什么?
Deno跟Node.js本质是一样的,都是JavaScript 和 TypeScript 运行时环境,基于 V8 引擎并采用 Rust 编程语言构建。
说白了就是Node.js的替代产品,我用它的主要原因有三点:
- 1、都是Rust语言构建的,跟Rust编译出来的wasm的“血缘”更接近
- 2、Deno天然支持TypeScript,不需要像Node.js还要tsc编译成js才能运行
- 3、Deno的项目结构和包管理方式简单清爽,写起来令人有一种心旷神怡的感觉
一、安装Deno
Deno安装很简单,几乎就是一行脚本就能搞定。
## 下载并安装deno
curl -fsSL https://deno.land/install.sh | sh
## 查看deno版本
deno -v
二、安装Rust
Rust安装脚本:
## 下载安装Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
## 查看cargo版本
cargo -V
三、使用wasmbuild模板编译wasm
为了简化开环境配置,我这里使用了工具wasmbuild
。主要用它来创建项目模板,将Rust代码编译除了生成wasm二进制文件,还有配套的js胶水代码。
wasmbuild
工具的源代码和详细使用说明可以参考:
- 首先创建,创建一个文件夹
rust_wasm
mkdir rust_wasm cd rust_wasm
- 然后在这个文件夹创建
deno.json
的配置文件,并写入一下内容{ "tasks": { "wasmbuild": "deno run -A jsr:@deno/wasmbuild@0.17.2" } }
- 接下来,使用wmsbuild的模板新建项目
deno task wasmbuild new
- 很快就,出现一个名为
rs_lib
目录,里面就是用于生成wasm的Rust源代码。打开rs_lib/src/lib.rs
我们可以看到已经有一些初始代码。
- 很快就,出现一个名为
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[wasm_bindgen]
pub struct Greeter {
name: String,
}
#[wasm_bindgen]
pub struct Greeter {
name: String,
}
#[wasm_bindgen]
impl Greeter {
#[wasm_bindgen(constructor)]
pub fn new(name: String) -> Self {
Self { name }
}
pub fn greet(&self) -> String {
format!("Hello {}!", self.name)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_adds() {
let result = add(1, 2);
assert_eq!(result, 3);
}
#[test]
fn it_greets() {
let greeter = Greeter::new("world".into());
assert_eq!(greeter.greet(), "Hello world!");
}
}
这段代码非常简单直观,即便像我这种不太熟悉Rust代码的人也能秒懂。值得注意的是,声明函数或者对象必须加上#[wasm_bindgen]
的属性标记,才能导出给JavaScript / TypeScript调用。其他的就过多解释了,我们继续往下走吧。
- Rust代码有了,接着就可以开始编译wasm了
deno task wasmbuild
编译成完成后,生成一个lib
文件夹,里面便是我们期盼很久的wasm文件了!
如果编译出现报错:linker 'cc' not found
,先把gcc安装好,然后再试。
apt-get install gcc
执行js脚本
项目目录里,还有一个叫mod.js
的代码没有介绍到。这是deno执行脚本入口,现在是时候打开一探了。
import { instantiate } from "./lib/rs_lib.generated.js";
const { add, Greeter } = await instantiate();
// adds
console.log(add(1, 1));
// greets
const greeter = new Greeter("world");
console.log(greeter.greet());
里面的代码很少,也很简单。它主要做两件事情:
- 引入刚刚编译rust代码生成wasm的胶水文件
./lib/rs_lib.generated.js
- 调用wasm里的函数
add
和类Greeter
的方法
我们马上执行命令看看效果。
deno run --allow-read mod.js
运行结果:
总结
为了弥补Node.js应用的先天性能不足,本文提供了一种新的思路:Rust 编译成 WebAssembly 能够提供接近原生的性能,给Node.js装载调用。计算密集型任务中,这通常比 JavaScript 执行得更快。
虽然,Rust 和 WebAssembly 在性能和安全性方面提供了显著的优势,但在易用性、工具集成和社区支持方面可能存在一些挑战。对于需要高性能计算的应用程序,或者那些对安全性有严格要求的项目,使用Rust和 WebAssembly 可能是一个很好的选择。然而,对于需要快速开发和广泛社区支持的项目,纯JavaScript 解决方案可能更好。
总之,大家可以根据自己的项目实际需要来选择更加合适的方案。