为什么在更新到Rust 1.38.0后,Vec::retain的运行速度变慢了?

8

在将 Rust 从版本 1.36.0 升级到 1.38.0 后,我注意到我的程序运行速度变慢了——大约慢了 50%。

使用 perf,我发现新版本中程序一半的时间花费在 alloc::vec::Vec<T>::retain 上。在旧版本中,这个函数甚至都没有出现过。为什么 retain 在 1.38.0 版本中会花费这么长时间呢?

retain 的调用如下所示:

some_vec.retain(|&x| x < DEADLINE);

deadline 是一个常量 u32,而 some_vec 则是一个 Vec<u32>

我在两个版本中都没有使用 retain 函数来运行程序。这种情况下,在平均值上 1.38.0 的速度仍然比 1.36.0 慢,但只有约 10% 左右,而不是之前看到的超过 50%。

回顾一下测试中发生的事情:

版本 1.36.0

  • 使用 retain:约 18 秒
  • 未使用 retain:约 11 秒

版本 1.38.0

  • 使用 retain:约 28 秒
  • 未使用 retain:约 12 秒

要获取可重现的示例,您可以尝试:

use std::time::Instant;

fn main() {
    let start = Instant::now();
    let mut my_vec: Vec<u32>;
    for _ in 0..100_000 {
        my_vec = (0..10_000).collect();
        my_vec.retain(|&x| x < 9000);
        my_vec.retain(|&x| x < 8000);
        my_vec.retain(|&x| x < 7000);
        my_vec.retain(|&x| x < 6000);
        my_vec.retain(|&x| x < 5000);
        my_vec.retain(|&x| (x < 5) & (x > 2));
    }
    let duration = start.elapsed();
    println!("Program took: {:?}", duration);
}

使用 cargo +1.36.0 run --release 命令,然后再执行 cargo +1.38.0 run --release 命令。

对于这个小例子,我得到了以下结果:

$ cargo +1.36.0 run --release
Program took: 4.624297719s

$ cargo +1.38.0 run --release
Program took: 8.293383522s

3
我认为这件事应该向Rust语言团队报告。 - Peter Hall
感谢您根据反馈改进了问题。您真的做得很好!我希望您能得到答案。 - trent
2
我进行了一些调查:1.37.0的速度与1.36.0一样快;夜间版本很慢。如果您忽略注释和函数的相对顺序,1.37和1.38生成的MIR看起来是相同的,这意味着:rustc更改了LLVM传递的数量/顺序/类型,或者rustc使用的LLVM版本在1.37和1.38之间发生了变化,在这种情况下,这是LLVM回归以及rustc回归。不幸的是,我不知道如何判断哪个是哪个。 - trent
这里是@Miguel提交的Bug报告:https://github.com/rust-lang/rust/issues/65970 - harmic
1个回答

2
总的来说,rust.godbolt.org 对于检查生成代码的质量非常有用(但不要忘记添加优化标志!)
在您的情况下,为 retain 生成的代码明显变得更糟了:https://rust.godbolt.org/z/ZhVCDg 因此,您应该将其作为性能回归报告给Rust。请点击这里进行报告。

1
我已经像@Peter Hall建议的那样向Rust报告了此问题。 - Miguel
4
对于好奇的人:https://github.com/rust-lang/rust/issues/65970 - mcarton

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