基于 Rust 重新实现的 nanoid —— 性能居然提升了 40+%

基于 Rust 重新实现的 nanoid —— 性能居然提升了 40+%_第1张图片

笔者在跟随 Rust 重写一切的风气下 基于 Rust 实现了 napi-nanoid

node 环境下相比原版 nanoid.js 性能提升 40+%

跑分: Linux x64 gnu, Intel® Xeon® Platinum 8370C CPU @ 2.80GHz, Node.js 16.15.1 (runs: 6842925183)

目录

    • 挑选技术栈
    • 创建 `napi-rs` 工程
    • 基于 Rust 实现 nanoid 生成逻辑
      • 实现调用方法入口
      • 实现最核心的 `format` 逻辑
    • 通过 benchmark 对比性能
      • 通过 `benny` + `bench` 实现性能测试脚本
      • 和原版性能对比
      • 和各种随机 id 生成库性能对比
    • 发布到 10+ 不同的平台
    • 仓库链接

挑选技术栈

笔者这里选择了 napi-rs 的作为基础实现 napi-nanoid
选择该技术栈的原因有两方面

  1. napi-rs 基于 Rust 生态,在实现 native NAPI 模块时可以更少考虑如 C++ 中内存申请释放的负担。

  2. napi-rs 的官方文档非常完整,提供了多个优秀的实践示例。

官网: napi.rs

创建 napi-rs 工程

基于 napi-rs/package-template

.
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── __test__ // 单元测试
├── benchmark // 性能测试
├── build.rs
├── index.d.ts
├── index.js
├── npm // 多平台入口
├── package.json
├── rustfmt.toml
├── src // source
├── tsconfig.json
└── yarn.lock

基于 Rust 实现 nanoid 生成逻辑

实现调用方法入口

调用方法设计了 .nanoid().non_secure() 高安全和非安全两个方法;
这里两个方法的设计参考了 nanoid.js

#[napi]
pub fn nanoid() -> String {
  format(nanoid::rngs::default, &nanoid::alphabet::SAFE, 21)
}

#[napi]
pub fn non_secure() -> String {
  format(nanoid::rngs::non_secure, &nanoid::alphabet::SAFE, 21)
}

实现最核心的 format 逻辑

实现过程中用到了宏来提供随机数生成的缓冲区;这里的实现思路也是参考了 nanoid.js

fn format(random: fn(usize) -> Vec<u8>, alphabet: &[char], size: usize) -> String {
  let bytes = &mut POOL.lock().unwrap();
  let mask = alphabet.len().next_power_of_two() - 1;

  let mut pointer = *POOL_OFFSET.lock().unwrap();
  let mut id = String::with_capacity(size);

  while id.len() < size {
    if pointer == POOL_SIZE {
      let buf = random(POOL_SIZE);
      for i in 0..POOL_SIZE {
        bytes[i] = buf[i];
      }
      pointer = 0;
    }
    let byte = bytes[pointer] as usize & mask;
    if alphabet.len() > byte {
      id.push(alphabet[byte]);
    }
    pointer += 1;
  }

  *POOL_OFFSET.lock().unwrap() = pointer;

  id
}

ps: 这里也同 nanoid.js 一样使用了 128 倍长度的缓冲空间

const POOL_SIZE_MULTIPLIER: usize = 128;
const DEFAULT_SIZE: usize = 21;
const POOL_SIZE: usize = DEFAULT_SIZE * POOL_SIZE_MULTIPLIER;

lazy_static! {
  static ref POOL: Mutex<[u8; POOL_SIZE]> = Mutex::new([0; POOL_SIZE]);
  static ref POOL_OFFSET: Mutex<usize> = Mutex::new(POOL_SIZE);
}

通过 benchmark 对比性能

通过 benny + bench 实现性能测试脚本

import benny from 'benny'
import benchmark, { Event } from 'benchmark'
const bench = new benchmark.Suite()

可参考代码: benchmark/bench.ts

和原版性能对比

  js-nanoid:
    3 392 881 ops/s, ±0.55%

  napi-nanoid:
    5 113 763 ops/s, ±0.12%

  js-nanoid (non-secure):
    1 875 245 ops/s, ±0.14%

  napi-nanoid (non-secure):
    5 237 554 ops/s, ±0.11% 

ps: 可以看出 napi-nanoid 版相比 js-nanoid 版本不管在安全还是非安全场景下都至少提升了 40+% 的性能表现。

跑分: (runs: 6842925183)

和各种随机 id 生成库性能对比

shortid                      24,084 ops/sec
cuid                        105,736 ops/sec
secure-random-string        207,409 ops/sec
uuid                        840,460 ops/sec
js-nanoid (non-secure)    1,826,354 ops/sec
js-nanoid (secure)        3,171,036 ops/sec
napi-nanoid (secure)      4,837,387 ops/sec
napi-nanoid (non-secure)  4,977,971 ops/sec
crypto.randomUUID        12,152,367 ops/sec
hyperid                  16,554,640 ops/sec

跑分: Linux x64 gnu, Intel® Xeon® Platinum 8370C CPU @ 2.80GHz, Node.js 16.15.1

跑分: (runs: 6842925183)

发布到 10+ 不同的平台

这里直接通过 napi-rs 官方的脚手架 + github workflow 实现发布到多个平台

node12 node14 node16
Windows x64
Windows x32
Windows arm64
macOS x64
macOS arm64
Linux x64 gnu
Linux x64 musl
Linux arm gnu
Linux arm64 gnu
Linux arm64 musl
Android arm64
Android armv7
FreeBSD x64

ps: 使用时无需关注平台 依然仅需引用 napi-nanoidnpm 入口包即可

const { nanoid } = require('napi-nanoid');

nanoid() // => AeogKAGjUMX6mqB4sMzWe

仓库链接

Repo: rustq/napi-nanoid

欢迎交流讨论 欢迎大家的 ☆

你可能感兴趣的:(rust,前端,node.js)