Rust 纪元第 382 周最佳 crate:ibig 的实践,以及和 num crate 的比较

2022-06-30 16:38:57 浏览数 (1)

Rust 纪元第 382 周,评出的周最佳 crate 是大数计算相关的 ibig。目前在 github 星星数量不足 50,还处于开发初期。

虽然以前的 Rust 开发中,大数计算方面使用的是 num crate 的 BigIntBigUint,完全满足需求。但是查阅到 ibig 提供的基准测试,性能挺不错。所以本文结合目前使用较广的大数计算 crate num,采用 2 种阶乘的不同实现方式,进行实践。尝试一下,看是否进行 crate 替换。

大数计算的概念,就不赘述。我们设想一个 1000000000 甚至更大的阶乘,不使用大数计算相关 crate,显然是跑不起来的。下面,我们使用 numibig 进行测试和比较。

准备

为了仅测试 numibig,我们创建一个单独的工程,并引入 chrono 进行时间的简单计算。执行如下命令:

代码语言:javascript复制
cargo new bigint
cd ./bigint
cargo add num ibig chrono

阶乘测试和比较

第一种阶乘实现方式

如上一步所示,我们使用的都是最新版本。下面,我们进行阶乘编码的实现,采用两种方式编写。先看第一种:

代码语言:javascript复制
use chrono::prelude::*;
use ibig::prelude::*;
use num::bigint::{BigInt, ToBigInt};

fn factorial_num1(x: u32) -> BigInt {
    if let Some(mut factorial) = 1.to_bigint() {
        for i in 1..(x   1) {
            factorial = factorial * i;
        }
        factorial
    } else {
        panic!("阶乘计算失败");
    }
}

fn factorial_ibig1(x: u32) -> UBig {
    if let Some(mut factorial) = Some(UBig::from(1 as u16)) {
        for i in 1..(x   1) {
            factorial = factorial * UBig::from(i);
        }
        factorial
    } else {
        panic!("阶乘计算失败");
    }
}

fn main() {
    let n: u32 = 100;

    // num crate 大数计算
    let start_num: DateTime<Local> = Local::now();
    println!("{}! 阶乘,num crate 计算:{:#x}", n, factorial_num1(n));

    let end_num: DateTime<Local> = Local::now();
    let time_num = end_num - start_num;

    println!("{:?}", &time_num);

    // ibig-rs crate 大数计算
    let start_ibig: DateTime<Local> = Local::now();
    println!("{}! 阶乘,ibig crate 计算:{:#x}", n, factorial_ibig1(n));

    let end_ibig: DateTime<Local> = Local::now();
    let time_ibig = end_ibig - start_ibig;

    println!("{:?}", &time_ibig);

    // ibig-rs 是否更快?
    println!("ibig-rs 快了这么多:{:#?}", time_num - time_ibig);
}

我们以发布模式命令 cargo run --release 执行计算,返回结果大抵如下所示(secs 正值时表示 num 慢,否则快):

代码语言:javascript复制
cargo run --release

100! 阶乘,num crate 计算:0x1b30964ec395dc24069528d54bbda40d16e966ef9a70eb21b5b2943a321cdf10391745570cca9420c6ecb3b72ed2ee8b02ea2735c61a000000000000000000000000
Duration { secs: 0, nanos: 1093700 }

100! 阶乘,ibig crate 计算:0x1b30964ec395dc24069528d54bbda40d16e966ef9a70eb21b5b2943a321cdf10391745570cca9420c6ecb3b72ed2ee8b02ea2735c61a000000000000000000000000
Duration { secs: 0, nanos: 343900 }

ibig-rs 快了这么多:Duration {
    secs: 0,
    nanos: 749800,
}

因为 100 以上的阶乘中,计算结果非常大,非常占页面空间,所以本文页面就不做结果展示。如果感兴趣,请你通过修改 n 值,进行测试。

笔者的测试结果,在 1000 以下的阶乘中,ibig 确实是快了一些,但没有超过 1 秒。在 10000 时,互有领先,总体来说 num 还是性能占优,和 ibig 相比大约 7:3 的优势。当 100000 时,num 会快到 4 秒左右;大于 100000 及以后,运行很慢,笔者只跑了一次,测试结果不具实际意义。

第二种阶乘实现方式

编码方式的不同,对于性能也有一定的影响,所以细读 ibig 的文档后,进行了第二种阶乘实现:

代码语言:javascript复制
use chrono::prelude::*;
use ibig::prelude::*;
use num::bigint::{BigInt, ToBigInt};

fn factorial_num2(a: u32, b: u32) -> BigInt {
    if b == a   1 {
        a.to_bigint().unwrap()
    } else {
        let mid = a   (b - a) / 2;
        factorial_num2(a, mid) * factorial_num2(mid, b)
    }
}

fn factorial_ibig2(a: u32, b: u32) -> UBig {
    if b == a   1 {
        UBig::from(a)
    } else {
        let mid = a   (b - a) / 2;
        factorial_ibig2(a, mid) * factorial_ibig2(mid, b)
    }
}

fn main() {
    let n: u32 = 100000;

    // num crate 大数计算
    let start_num: DateTime<Local> = Local::now();
    println!(
        "{}! 阶乘,num crate 计算:{:#x}",
        n,
        factorial_num2(1, n   1)
    );

    let end_num: DateTime<Local> = Local::now();
    let time_num = end_num - start_num;

    println!("{:?}", &time_num);

    // // ibig-rs crate 大数计算
    let start_ibig: DateTime<Local> = Local::now();
    println!(
        "{}! 阶乘,ibig crate 计算:{:#x}",
        n,
        factorial_ibig2(1, n   1)
    );

    let end_ibig: DateTime<Local> = Local::now();
    let time_ibig = end_ibig - start_ibig;

    println!("{:?}", &time_ibig);

    // ibig-rs 是否更快?
    println!("ibig-rs 快了这么多:{:#?}", time_num - time_ibig);
}

我们同样以发布模式命令 cargo run --release 执行计算,返回结果大抵和第一种方式类同。

但是性能比较,有了变化。在 10000 以下时,ibig 同样是占优的。在 10000 这个阶乘层次,第 1、2 个 10 次都是 5:5,第 3 个 10 次 num 快了一次。100000 及以上,等待时间过长,笔者只跑了一次,测试结果不具实际意义。

所以,目前所使用的 crate num 暂时还是不考虑替换了。

ibig 官方的基准测试

最后,附上 ibig 官方的基准测试值:每个基准测试运行 5 次,每次重复计算至少 10 秒,结果使用中位数运行。

正如前文笔者所述,代码的不同,平台的不同等,测试性能差别有可能也很大。所以这个基准测试结果,仅能参考。所谓实践出真知,还需要自己实际使用后,才晓得是否合适。

谢谢您的阅读。

0 人点赞