我在Rust中实现了Miller-Rabin强伪素数测试,使用BigUint支持任意大素数.要计算5到10^6之间的数字,大约需要40秒和cargo run --release秒.

我用Java的BigInteger实现了同样的算法,同样的测试用了10秒才完成.Rust 的速度似乎要慢4倍.我认为这是由num::bigint的实施引起的.

这仅仅是目前num::bigint的状态,还是有人能在我的代码中发现任何明显的改进?(主要是关于我是如何使用这种语言的.无论我对算法的实现是好是坏,这两种语言的实现几乎完全相同——因此不会造成性能上的差异.)

我确实注意到,由于Rust的所有权模式,需要clone()个,这可能会在一定程度上影响速度.但我想这是没办法的,对吗?

以下是代码:

extern crate rand;
extern crate num;
extern crate core;
extern crate time;

use std::time::{Duration};
use time::{now, Tm};

use rand::Rng;
use num::{Zero, One};
use num::bigint::{RandBigInt, BigUint, ToBigUint};
use num::traits::{ToPrimitive};
use num::integer::Integer;
use core::ops::{Add, Sub, Mul, Div, Rem, Shr};

fn find_r_and_d(i: BigUint) -> (u64, BigUint) {
    let mut d = i;
    let mut r = 0;
    loop {
        if d.clone().rem(&2u64.to_biguint().unwrap()) == Zero::zero() {
            d = d.shr(1usize);
            r = r + 1;
        } else {
            break;
        }
    }
    return (r, d);
}

fn might_be_prime(n: &BigUint) -> bool {
    let nsub1 = n.sub(1u64.to_biguint().unwrap());
    let two = 2u64.to_biguint().unwrap();

    let (r, d) = find_r_and_d(nsub1.clone());
    'WitnessLoop: for kk in 0..6u64 {
        let a = rand::thread_rng().gen_biguint_range(&two, &nsub1);
        let mut x = mod_exp(&a, &d, &n);
        if x == 1u64.to_biguint().unwrap() || x == nsub1 {
            continue;
        }
        for rr in 1..r {
            x = x.clone().mul(x.clone()).rem(n);
            if x == 1u64.to_biguint().unwrap() {
                return false;
            } else if x == nsub1 {
                continue 'WitnessLoop;
            } 
        }
        return false;
    }
    return true;
}

fn mod_exp(base: &BigUint, exponent: &BigUint, modulus: &BigUint) -> BigUint {
    let one = 1u64.to_biguint().unwrap();
    let mut result = one.clone();
    let mut base_clone = base.clone();
    let mut exponent_clone = exponent.clone();

    while exponent_clone > 0u64.to_biguint().unwrap() {
        if exponent_clone.clone() & one.clone() == one {
            result = result.mul(&base_clone).rem(modulus);
        } 
        base_clone = base_clone.clone().mul(base_clone).rem(modulus);
        exponent_clone = exponent_clone.shr(1usize);
    }
    return result;
}

fn main() {  
    let now1 = now();

    for n in 5u64..1_000_000u64 {
        let b = n.to_biguint().unwrap();
        if might_be_prime(&b) {
            println!("{}", n);
        }
    }

    let now2 = now();
    println!("{}", now2.to_timespec().sec - now1.to_timespec().sec);
}  

推荐答案

你可以很容易地删除大部分克隆.BigUint的所有ops特性也适用于&BigUint的操作,而不仅仅是使用值.这样,它的速度会更快,但仍然是Java的一半左右...

此外(与性能无关,只是可读性),您不需要显式地使用addsubmulshr;它们覆盖常规的+-*>>运算符.

例如,你可以像这样重写might_be_primemod_exp,这已经在我的机器上提供了很好的加速(在avg上从40秒到24秒):

fn might_be_prime(n: &BigUint) -> bool {
    let one = BigUint::one();
    let nsub1 = n - &one;
    let two = BigUint::new(vec![2]);
    let mut rng = rand::thread_rng();

    let (r, mut d) = find_r_and_d(nsub1.clone());
    let mut x;
    let mut a: BigUint;
    'WitnessLoop: for kk in 0..6u64 {
        a = rng.gen_biguint_range(&two, &nsub1);
        x = mod_exp(&mut a, &mut d, &n);
        if &x == &one || x == nsub1 {
            continue;
        }
        for rr in 1..r {
            x = (&x * &x) % n;
            if &x == &one {
                return false;
            } else if x == nsub1 {
                continue 'WitnessLoop;
            } 
        }
        return false;
    }
    true
}

fn mod_exp(base: &mut BigUint, exponent: &mut BigUint, modulus: &BigUint) -> BigUint {
    let one = BigUint::one();
    let zero = BigUint::zero();
    let mut result = BigUint::one();

    while &*exponent > &zero {
        if &*exponent & &one == one {
           result = (result * &*base) % modulus;
        }
        *base = (&*base * &*base) % modulus;
        *exponent = &*exponent >> 1usize;
    }
    result
}

注意,我已经移动了println!时间不合适,所以我们没有对IO进行基准测试.

fn main() {  
    let now1 = now();

    let v = (5u64..1_000_000u64)
        .filter_map(|n| n.to_biguint())
        .filter(|n| might_be_prime(&n))
        .collect::<Vec<BigUint>>();

    let now2 = now();
    for n in v {
        println!("{}", n);
    }
    println!("time spent seconds: {}", now2.to_timespec().sec - now1.to_timespec().sec);
} 

Rust相关问答推荐

在rust中如何修改一个盒装函数并将其赋回?

收集RangeInclusive T到Vec T<><>

rust 迹-内存管理-POP所有权-链表

使用极点数据帧时,找不到枚举结果的方法lazy()

我如何制作一个变异迭代器来锁定内部数据直到删除?

`RwLockWriteGuard_,T`不实现T实现的特征

在0..1之间将U64转换为F64

有没有一种方法可以创建一个闭包来计算Rust中具有随机系数的n次多项式?

为什么`tokio::main`可以直接使用而不需要任何导入?

为什么 Rust 需要可变引用的显式生命周期而不是常规引用?

从字节数组转换为字节元组和字节数组时,为什么 Transmute 会对字节重新排序?

Rust Axum 框架 - 解包安全吗?

在Rust中实现Trie数据 struct 的更好方式

在异步 Rust 中,Future 如何确保它只调用最近的 Waker?

SDL2 没有在终端键上触发?

更好的方法来模式匹配带有 Rust 的窥视孔装配说明窗口?

如何在 Rust 中编写修改 struct 的函数

如何在 Rust 中创建最后一个元素是可变长度数组的 struct ?

预期类型参数,发现不透明类型

如何为枚举中的单个或多个值返回迭代器