名词解释
- 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.node
is the Node.js addon binary file。js 就是通过 require 该文件获得我们的目标函数。index.js
is the generated JavaScript binding file which helps you export all the stuffs in the addon to the package caller. 这是入口文件- And the
index.d.ts
is 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…