有没有办法优化这个代码,所以它不会溢出堆栈?

时间:2016-06-18 17:42:31

标签: recursion optimization rust stack-overflow

我正在研究第三个项目Euler问题:

fn main() {
    println!("{}", p3());
}

fn p3() -> u64 {
    let divs = divisors(1, 600851475143, vec![]);
    let mut max = 0;
    for x in divs {
        if prime(x, 0, false) && x > max {
            max = x
        }
    }
    max
}

fn divisors(i: u64, n: u64, div: Vec<u64>) -> Vec<u64> {
    let mut temp = div;
    if i * i > n {
        temp
    } else {
        if n % i == 0 {
            temp.push(i);
            temp.push(n / i);
        }
        divisors(i + 2, n, temp)
    }
}

fn prime(n: u64, i: u64, skip: bool) -> bool {
    if !skip {
        if n == 2 || n == 3 {
            true
        } else if n % 3 == 0 || n % 2 == 0 {
            false
        } else {
            prime(n, 5, true)
        }
    } else {
        if i * i > n {
            true
        } else if n % i == 0 || n % (i + 2) == 0 {
            false
        } else {
            prime(n, i + 6, true)
        }
    }
}

600851475143是某个时刻导致其溢出的值。如果我将其替换为10 10 数量级或更小的任何值,它将返回一个答案。在将其作为递归解决方案的同时,有没有办法:

  1. 增加堆栈大小?
  2. 优化我的代码,使其不会返回fatal runtime: stack overflow错误?
  3. 我知道这可以迭代完成,但我宁愿不这样做。

3 个答案:

答案 0 :(得分:2)

包含600 * 10 9 u64 s的向量意味着您需要4.8 TB的RAM或交换空间。

我确定你不需要这个问题,你在这里缺少一些数学知识:扫描直到600851475143的平方根就足够了。您也可以使用Sieve of Eratosthenes

来加速该计划

Project Euler很高兴提高你的数学技能,但它对你的任何编程语言都没有帮助。为了学习Rust,我从Exercism开始。

答案 1 :(得分:2)

执行一些优化,例如在检查其因素时是否达到数字的平方根,以及是否为素数,我得到了:

fn is_prime(n: i64) -> bool {
    let float_input = n as f64;
    let upper_bound = float_input.sqrt() as i64;

    for x in 2..upper_bound + 1 {
        if n % x == 0 {
            return false;
        }
    }
    return true;
}

fn get_factors(n: i64) -> Vec<i64> {
    let mut factors: Vec<i64> = Vec::new();

    let float_input = n as f64;
    let upper_bound = float_input.sqrt() as i64;

    for x in 1..upper_bound + 1 {
        if n % x == 0 {
            factors.push(x);
            factors.push(n / x);
        }
    }
    factors
}

fn get_prime_factors(n: i64) -> Vec<i64> {
    get_factors(n)
        .into_iter()
        .filter(|&x| is_prime(x))
        .collect::<Vec<i64>>()
}

fn main() {
    if let Some(max) = get_prime_factors(600851475143).iter().max() {
        println!("{:?}", max);
    }
}

在我的机器上,此代码运行速度非常快,没有溢出。

./problem003  0.03s user 0.00s system 90% cpu 0.037 total

答案 2 :(得分:1)

如果你真的不想要迭代版本:

首先,确保使用优化(rustc -Ocargo --release)进行编译。没有它,Rust的TCO就没有机会了。你的divisors函数是尾递归的,但似乎在递归堆栈中上下移动Vec令人困惑,LLVM就错过了这个事实。我们可以在这里使用一个引用来帮助编译器:

fn divisors(i: u64, n: u64, mut div: Vec<u64>) -> Vec<u64> {
    divisors_(i, n, &mut div);
    div
}

fn divisors_(i: u64, n: u64, div: &mut Vec<u64>) {
    if i * i > n {
    } else {
        if n % i == 0 {
            div.push(i);
            div.push(n / i);
        }
        divisors_(i + 2, n, div)
    }
}

在我的机器上,更改使代码不再是段错误。

如果你想增加堆栈大小,你应该在一个单独的线程中运行你的函数,堆栈大小增加(使用std::thread::Builder::stack_size

Rust保留了become关键字以保证尾递归, 因此,将来您可能只需要在代码中添加一个关键字即可使其正常运行。