我用下面两段代码编码Varint,NodeJS大约需要900毫秒,而Rust大约需要2700毫秒.为什么业绩差距这么大?

内存分配看起来更耗时,NodeJS有没有额外的内存分配优化?

NodeJS版本:v20.10.0

Rust版本:1.75.0

JavaScript代码:

// node index.js
const { performance } = require('node:perf_hooks')

function encode_varint(num) {
    let buf_size = 0;
    let cmp_number = num;

    while (cmp_number) {
        cmp_number >>>= 7
        buf_size += 1;
    }

    const buf = new Array(buf_size)
    let temp_num = num;
    let index = 0;

    while (temp_num > 0) {
        if ((temp_num >>> 7) !== 0) {
            buf[index++] = 0x80 | (temp_num & 0x7f)
            temp_num >>>= 7;
        } else {
            buf[index++] = temp_num & 0x7f
            break
        }
    }
    
    return buf;
}
const N = 100_000_000;

const start = performance.now();

for (let i = 0; i < N; i++) {
    const num = 999_999_999_999_999;
    const buf = encode_varint(num);
    // console.log(buf)
}

const end = performance.now();
const elapsed = end - start;
console.log(elapsed);

铁 rust 代码:

// cargo r --release
use std::time;

fn encode_varint(num: usize) -> Vec<u8> {
    let buf_size = {
        let mut size = 1;
        let mut cmp_number = num;
        while cmp_number > 0 {
            cmp_number >>= 7;
            size += 1;
        }
        size
    };
    let mut buf: Vec<u8> = Vec::with_capacity(buf_size);

    let mut temp_num = num;
    while temp_num > 0 {
        if (temp_num >> 7) != 0 {
            buf.push((0x80 | (temp_num & 0x7f)) as u8);
            temp_num >>= 7;
        } else {
            buf.push((temp_num & 0x7f) as u8);
            break;
        }
    }

    buf
}

const N: u32 = 100_000_000;

fn main() {

    let start = time::Instant::now();
    let num = 999_999_999_999_999;

    for _ in 0..N {
        let _buf = encode_varint(num);
        // dbg!(_buf);
    }
    
    let end = time::Instant::now();

    let elapsed = end - start;
    println!("{}", elapsed.as_millis());
}

推荐答案

两个程序的计算是不一样的. 在Rust中,我们得到[255, 255, 153, 166, 234, 175, 227, 1],而在JS中,我们得到[ 255, 255, 153, 166, 10 ].

这是因为在JS中,我们不能一劳永逸地 Select 数值表达式的确切类型;它会根据运算而变化. 例如,初始值很大,但保持在双精度浮点(number类型)的尾数中而没有损失. 像JS中的所有按位操作一样,操作>>> 7强制转换为32位整数(这在the documentation中有描述,这是asm.js背后的原始 idea ,导致了Wasm),然后在JS中发生截断,而在Rust中没有. 例如,如果我们在JS中的每个迭代开始时打印temp_num >>> 7的结果,我们会看到

NEXT 21597439
NEXT 168729
NEXT 1318
NEXT 10
NEXT 0

当我们在铁 rust 中看到

NEXT 7812499999999
NEXT 61035156249
NEXT 476837158
NEXT 3725290
NEXT 29103
NEXT 227
NEXT 1
NEXT 0

从这里开始,我们不能比较两个版本的性能,因为它们不是等同的.

另一方面,正如 comments 中所述,JS的分配策略可能更好,因为这个微基准的形状被人为地重复了(但在真正的问题上仍然是这样吗?) cargo flamegraph证实了这一点,因为大部分时间都花在分配/释放上. 为了提高性能,我们可以使函数变得清晰并始终改变相同的向量(作为&mut传递,在main()中一次性创建),而不是在每次迭代时构建一个新的向量(这也将消除计算buf_size的需要).

Node.js相关问答推荐

在child_Process中持久运行SSH命令

Azure虚拟机上的JS Express:可疑请求?

容器端口是容器内 node 应用程序的端口吗?

Jest由于UUID而无法解析测试,即使在Jest中启用ESModule支持后也是如此

请求正文未定义

使用 playwright 获取页面中同一 url 的所有响应

将图像添加到多个产品的条带 checkout 会话中

如何获取mongoose中单个id数据的记录

未授权使用联合身份未授权用户角色从 Amplify graphQL 访问类型 Y 上的 X

我应该转译我的 TypeScript 应用程序吗?

'{ id: string; 类型的参数}' 不可分配给FindOneOptions类型的参数

Node.js、Cygwin 和 Socket.io 走进一家wine 吧……Node.js 抛出 ENOBUFS,所有人都死了

Dart 语言比 JavaScript (Node.js) 有什么好处

TypeError:winston.Logger 不是带有winston 和morgan 的构造函数

React Native ios build:找不到 node

npm package.json 操作系统特定脚本

如何忽略文件 grunt uglify

yarn 和 npm 的主要区别是什么?

npm install packagename --save-dev 不更新 package.json

Mongoose - 验证邮箱语法