名词解释
- NAPI Node-API
- NAPI-RS is a framework for building pre-compiled Node.js addons in Rust. 也就是说以前要用 C++ 写现在可以通过这个框架用 Rust 写!
目标
Rust 编写代码
fn fib(n: u32) {
match n {
1 | 2 => 1,
_ => fib(n - 1) + fib(n - 2)
}
}
Node.js 调用(当然 esm 和 commonjs 都需要支持)
import { fib } from './index.js'
console.log(fib(3)) // 2
console.log(fib(4)) // 3
console.log(fib(5)) // 5
console.log(fib(6)) // 8
console.log(fib(7)) // 13
console.log(fib(8)) // 21
console.log(fib(9)) // 34
console.log(fib(10)) // 55
然后对比二者的性能差异
import { fib } from './index.js'
console.time('napi-rs')
...
console.timeEnd('napi-rs')
console.time('node')
...
console.timeEnd('node')
此次需要安装 crate,故需要按照第一课配置好 crate registry 以便能快速安装包,配置后尝试在 hello_world 中安装 rand,验证下速度。
第一次会下载整个 index,故比较慢,后续 cargo add 会很快。我们试试安装一个随机数的 crate 看看我们的代理是否配置成功。
安装:
❯ cargo add rand
Updating `rsproxy` index
Fetch [===> ] 19.97%, 3.07MiB/s
main.rs 使用:
use rand::Rng;
fn main() {
println!("Hello, world! 你好,世界!");
let num = rand::thread_rng().gen_range(1..=100);
println!("{num}")
}
运行:cargo run
Hello, world! 你好,世界!
17
成功了!我们第一次使用了标准库之外的外部库,这是一个小里程碑事件!记下来我们回到本文的目标。
napi-rs
按照 napi.rs/docs/introd… 文档操作会遇到
aarch64-apple-darwin vs x86_64-apple-darwin 如何选择?
我们通过下面表格知道 aarch64 x86_64 到底是什么。
| CPU Architecture | Description |
|---|---|
x86_64/x86/amd64 | Same name for 64-bit AMD/Intel CPUs |
AArch64/arm64/ARMv8/ARMv9 | Same name for 64-bit ARM CPUs |
i386 | 32-bit AMD/Intel CPUs |
AArch32/arm/ARMv1 to ARMv7 | Same name for 32-bit ARM CPUs |
rv64gc/rv64g | Same name for 64-bit RISC-V CPUs |
ppc64le | 64-bit PowerPC CPUs with little-endian memory ordering |
表格来自
- https://www.linuxconsultant.org/arm-vs-aarch64-vs-amd64-vs-x86_64-vs-x86-whats-the-difference/ - https://itsfoss.com/arm-aarch64-x86_64/通过命令行 $ arch 输出 arm64,知道我们本地的架构(arch),而且我们是苹果电脑(app-darwin),故选择 aarch64-apple-darwin。
继续执行会报错,因为我们并未安装 yarn,可以忽略,因为我们自己执行 npm install 也可以安装依赖。
/bin/sh: yarn: command not found
- npm install
- npm run build
此时多了三个文件:
xx.darwin-x64.nodeis the Node.js addon binary file。js 就是通过 require 该文件获得我们的目标函数。index.jsis the generated JavaScript binding file which helps you export all the stuffs in the addon to the package caller. 这是入口文件- And the
index.d.tsis the generated TypeScript definition file. TS 类型文件,为导出的目标函数提供类型注解。
接下里测试下构建是否成功。
- npm test
__test__/index.spec.mjs
import test from 'ava'
import { sum } from '../index.js'
console.log(sum.toString())
test('sum from native', (t) => {
t.is(sum(1, 2), 3)
})
单测成功说明我们已经能通过 js 调用 Rust 函数了。Another milestone event!
为了满足我们的好奇心,我们打印下 sum,可以发现 function sum() { [native code] } 说明确实变成了 node.js addon。
接下来是重头戏:
实现 Fibonacci 函数
TDD
单测先行
import test from 'ava'
import { fib } from '../index.js'
test('fib from native', (t) => {
t.is(fib(1), 1)
t.is(fib(2), 1)
t.is(fib(3), 2)
t.is(fib(4), 3)
t.is(fib(5), 5)
t.is(fib(6), 8)
t.is(fib(7), 13)
t.is(fib(8), 21)
t.is(fib(9), 34)
t.is(fib(10), 55)
})
src/lib.rs
#![deny(clippy::all)]
#[macro_use]
extern crate napi_derive;
#[napi]
pub fn fib(n: u32) -> u32 {
match n {
1 | 2 => 1,
_ => fib(n - 1) + fib(n - 2)
}
}
性能对比
test/perf.js
const { fib } = require('..')
const { jsFib } = require('./js-fib')
const process = require('node:process')
const n = Number(process.argv.at(-1)) || 1
console.time('fib from native when n = ' + n)
fib(n)
console.timeEnd('fib from native when n = ' + n)
console.time('fib from javascript when n = ' + n)
jsFib(n)
console.timeEnd('fib from javascript when n = ' + n)
结果
rust 的斐波那契函数性能是 js 的 3 倍,当超过 42 性能差异更加明显
❯ node __test__/perf.js 42
fib from native when n = 42: 501.801ms
fib from javascript when n = 42: 1.494s
❯ node __test__/perf.js 44
fib from native when n = 44: 1.314s
fib from javascript when n = 44: 3.849s
总结
本文成功通过 napi-rs 生成了供 js 调用的 Rust 函数,性能有了三倍的提升,过程还是比较顺利的 😄。有了这个框架,我们在日常工作中如果 node.js 遇到性能问题多了一个称手的工具。
更多阅读
- toml 语法 toml.io/cn/v1.0.0
- toml to json playground transform.tools/toml-to-jso…
- 如何在 markdown 中隐藏展开内容
- 完整代码 github.com/legend80s/n…