使用 cargo test --release 会导致堆栈溢出,为什么 cargo bench 没有发生?

19

在尝试编写优化的DSP算法时,我想知道堆栈分配和堆分配之间的相对速度以及堆栈分配数组的大小限制。我意识到有一个堆栈帧大小限制,但我不明白为什么下面的代码会运行并生成看似真实的基准测试结果,但在使用cargo test --release运行时会失败并出现堆栈溢出。

#![feature(test)]
extern crate test;

#[cfg(test)]
mod tests {
    use test::Bencher;

    #[bench]
    fn it_works(b: &mut Bencher) {
        b.iter(|| { let stack = [[[0.0; 2]; 512]; 512]; });
    }
}

1
你有检查过生成的LLVM IR/汇编码吗? - Matthieu M.
1个回答

33
为了让事情更加清晰,需要注意的是,你的数组大小为8×2×512×512 = 4 MiB。 崩溃但没有,因为“test”在一个新线程中调用函数,而“bench”在主线程中调用它。
主线程的默认堆栈大小通常为8 MiB,因此该数组将占用可用堆栈的一半。虽然这很多,但仍有空间可用,因此基准测试正常运行。
然而,新线程的堆栈大小通常要小得多。在Linux上,它为2 MiB,其他平台可能会更小。因此,你的4 MiB数组很容易溢出线程的堆栈并导致堆栈溢出/段错误。
你可以通过设置RUST_MIN_STACK环境变量来增加新线程的默认堆栈大小。
$ RUST_MIN_STACK=8388608 cargo test 

"cargo test" 命令会并行运行测试用例以提高总体测试时间,而基准测试会依次在同一线程中运行以减少噪音。
由于堆栈大小有限,将数组分配到堆栈上是一个坏主意。您必须将其存储在堆上(使用 "box")或作为全局 "static mut"。

1
这个回答听起来很可信,但你能提供一些参考资料吗?我有点惊讶的是为什么不在单独的线程中运行benchmarks,只是禁用并发,因为线程也提供了一个很好的panic防火墙(而且我也期望会有代码重用)。 - Shepmaster
4
我在LLDB中运行了它,并注意到当我将数组大小增加到5120时,测试在"线程#1"(主线程)中使用--bench标志运行,在"线程#2"中使用--test标志运行。相关的代码似乎在这里:https://github.com/rust-lang/rust/blob/8c4f2c64c6759a82f143e23964a46a65c67509c9/src/libtest/lib.rs#L1337-L1369。 --test会生成一个新的线程,而--bench则直接运行。此外,catch_unwind不会启动新线程。 - kennytm
1
没错,现在有catch_unwind了。在古老的时代,唯一“捕获”恐慌的方法是通过单独的线程。 - Shepmaster
太好了,谢谢。我知道它对堆栈来说很大,也知道测试是并行运行的,尽管我没有意识到基准测试不是这样的。 - Josh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接