Рекурсивная функция вычисления факториалов приводит к переполнению стека
я попробовал рекурсивный факториальный алгоритм в Rust. Я использую эту версию компилятора:
rustc 1.12.0 (3191fbae9 2016-09-23)
cargo 0.13.0-nightly (109cb7c 2016-08-19)
код:
extern crate num_bigint;
extern crate num_traits;
use num_bigint::{BigUint, ToBigUint};
use num_traits::One;
fn factorial(num: u64) -> BigUint {
let current: BigUint = num.to_biguint().unwrap();
if num <= 1 {
return One::one();
}
return current * factorial(num - 1);
}
fn main() {
let num: u64 = 100000;
println!("Factorial {}! = {}", num, factorial(num))
}
я получил эту ошибку:
$ cargo run
thread 'main' has overflowed its stack
fatal runtime error: stack overflow
error: Process didn't exit successfully
как исправить? И почему я вижу эту ошибку при использовании ржавчина?
3 ответов
Rust не имеет исключения хвостового вызова, поэтому ваша рекурсия ограничена размером стека. Это может быть функция для ржавчины в будущем (вы можете прочитать больше об этом в часто задаваемые вопросы Руст), но в то же время вам придется либо не повторить столь глубок и использовать циклы.
просто в качестве альтернативы.. (Не рекомендую)
ответ Matts верен до некоторой степени. Существует ящик под названием stacker
(здесь), который может искусственно увеличить размер стека для использования в рекурсивных алгоритмах. Он делает это, выделяя некоторую память кучи для переполнения.
в качестве предупреждения... это занимает очень много времени ... но он работает, и он не взрывает стек. Компиляция с оптимизациями приносит его вниз, но это все еще довольно медленно. Вы, вероятно, получите лучший perf от цикла, как предлагает Мэтт. Я думал, что все равно выброшу это.
extern crate num_bigint;
extern crate num_traits;
extern crate stacker;
use num_bigint::{BigUint, ToBigUint};
use num_traits::One;
fn factorial(num: u64) -> BigUint {
// println!("Called with: {}", num);
let current: BigUint = num.to_biguint().unwrap();
if num <= 1 {
// println!("Returning...");
return One::one();
}
stacker::maybe_grow(1024 * 1024, 1024 * 1024, || {
current * factorial(num - 1)
})
}
fn main() {
let num: u64 = 100000;
println!("Factorial {}! = {}", num, factorial(num));
}
Я прокомментировал debug println
s.. вы можете раскомментировать их, если хотите.
почему?
это переполнение стека, которое происходит всякий раз, когда нет стековой памяти. Например, память стека используется
- локальные переменные
- аргументов функции
- возвращаемые значения
рекурсия использует много памяти стека, потому что для каждого рекурсивного вызова память для всех локальных переменных, аргументов функции,... должен быть выделен в стеке.
как исправить?
очевидное решение-написать свой алгоритм нерекурсивным способом (вы должны сделать это, когда хотите использовать алгоритм в производстве!). Но вы также можете просто увеличить размер стека. В то время как размер стека основного потока не могу быть измененным, вы можете создать новый поток и установить определенный размер стека:
fn main() {
let num: u64 = 100_000;
// Size of one stack frame for `factorial()` was measured experimentally
thread::Builder::new().stack_size(num as usize * 0xFF).spawn(move || {
println!("Factorial {}! = {}", num, factorial(num));
}).unwrap().join();
}
код работает и, при выполнении через cargo run --release
(с оптимизацией!), выводит решение после расчета всего за пару секунд.
размер рамки стога измерения
в случае, если вы хотите знать, как размер кадра стека (требование памяти для один звонок) для factorial()
было измерено: я напечатал адрес аргумента функции num
в каждом factorial()
звоните:
fn factorial(num: u64) -> BigUint {
println!("{:p}", &num);
// ...
}
разница между двумя последовательными адресами вызовов является (более или менее) размером кадра стека. На мой машина, разница была чуть меньше 0xFF
(255), поэтому я просто использовал это как размер.
если вам интересно, почему размер кадра стека не меньше: компилятор Rust на самом деле не оптимизируется для этой метрики. Обычно это действительно не важно, поэтому оптимизаторы склонны жертвовать этим требованием памяти для лучшей скорости выполнения. Я взглянул на собрание, и в этом случае многие методы. Это означает, что локальные переменные других функций использование пространства стека!