根据我的测试,为什么 NodeJS 编码 Varint 比 Rust 快得多?

问题描述 投票:0回答:1

我使用以下两段代码对 Varint 进行编码,NodeJS 大约需要 900ms,而 Rust 大约需要 2700ms。为什么性能差距这么大?

看起来分配内存比较耗时,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());
}
node.js performance rust varint
1个回答
0
投票

两个程序的计算不一样。 通过

999_999_999_999_999
,我们在 Rust 中获得
[255, 255, 153, 166, 234, 175, 227, 1]
,而在 JS 中我们获得
[ 255, 255, 153, 166, 10 ]

这是因为在 JS 中我们无法一次性选择数值表达式的确切类型;它根据操作而变化。 例如,初始值很大,但没有损失地位于双精度浮点的尾数中。 操作

>>> 7
,与 JS 中的所有按位操作一样,强制转换为 32 位整数(这在文档中有描述,这是 WebAssembly 背后的原始想法,导致了 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

从这里开始,我们无法比较两个版本的性能,因为它们并不等同。

另一方面,正如评论中所述,由于这个微基准的人为重复形状,JS 中的分配策略可能更好(但在实际问题上仍然如此吗?)。

cargo flamegraph
证实了这一点,因为大部分时间都花在分配/释放上。 为了提高性能,我们可以使函数清晰并始终改变相同的向量(作为
&mut
传递,在
main()
中一次性创建),而不是在每次迭代时构建一个新向量(这也会删除需要计算
buf_size
)。

© www.soinside.com 2019 - 2024. All rights reserved.