【问题标题】:Why is this Rust program so slow? Did I miss something?为什么这个 Rust 程序这么慢?我错过了什么?
【发布时间】:2018-03-30 09:36:30
【问题描述】:

我阅读了Minimal distance in Manhattan metric,并在Rust 中重写了作者的“幼稚”实现。 C++ 变体是:

#include <utility>
#include <cstdio>
#include <cstdlib>

std::pair<int, int> pointsA[1000001];
std::pair<int, int> pointsB[1000001];

int main() {
    int n, t;
    unsigned long long dist;

    scanf("%d", &t);

    while(t-->0) {
        dist = 4000000000LL;
        scanf("%d", &n);

        for(int i = 0; i < n; i++) {
            scanf("%d%d", &pointsA[i].first, &pointsA[i].second);
        }

        for(int i = 0; i < n; i++) {
            scanf("%d%d", &pointsB[i].first, &pointsB[i].second);
        }

        for(int i = 0; i < n ;i++) {
            for(int j = 0; j < n ; j++) {
                if(abs(pointsA[i].first - pointsB[j].first) + abs(pointsA[i].second - pointsB[j].second) < dist)
                    dist = abs(pointsA[i].first - pointsB[j].first) + abs(pointsA[i].second - pointsB[j].second);
            }
        }
        printf("%lld\n", dist);
    }
}

Rust 变体是:

use std::io;
use std::io::BufReader;
use std::io::BufRead;

fn read_array(stdin: &mut BufReader<io::Stdin>, array_len: usize, points: &mut Vec<(i32, i32)>) {
    let mut line = String::new();
    for _ in 0..array_len {
        line.clear();
        stdin.read_line(&mut line).unwrap();
        let mut item = line.split_whitespace();
        let x = item.next().unwrap().parse().unwrap();
        let y = item.next().unwrap().parse().unwrap();
        points.push((x, y));
    }
}

fn manhattan_dist(a: &(i32, i32), b: &(i32, i32)) -> u32 {
    ((a.0 - b.0).abs() + (a.1 - b.1).abs()) as u32
}

fn main() {
    let mut line = String::new();
    let mut stdin = BufReader::new(io::stdin());
    stdin.read_line(&mut line).unwrap();
    let n_iters = line.trim_right().parse::<usize>().unwrap();
    let mut points_a = Vec::with_capacity(10000);
    let mut points_b = Vec::with_capacity(10000);
    for _ in 0..n_iters {
        line.clear();
        stdin.read_line(&mut line).unwrap();
        let set_len = line.trim_right().parse::<usize>().unwrap();
        points_a.clear();
        points_b.clear();
        read_array(&mut stdin, set_len, &mut points_a);
        read_array(&mut stdin, set_len, &mut points_b);
        let mut dist = u32::max_value();
        for i in points_a.iter() {
            for j in points_b.iter() {
                dist = std::cmp::min(manhattan_dist(i, j), dist);
            }
        }
        println!("{}", dist);
    }
}

然后,我用 Python 脚本生成了数据:

import random

ITER = 100
N = 10000
MAX_INT = 1000000

print("%d" % ITER)

for _ in range(0, ITER):
    print("%d" % N)
    for _ in range(0, N):
        print(random.randrange(-MAX_INT, MAX_INT + 1), random.randrange(1, MAX_INT + 1))
    for _ in range(0, N):
        print(random.randrange(-MAX_INT, MAX_INT + 1), random.randrange(-MAX_INT, 0))

并分别使用g++ -Ofast -march=nativerustc -C opt-level=3 编译这两个变体。时间是:

C++

real    0m7.789s
user    0m7.760s
sys     0m0.020s

生锈

real    0m28.589s
user    0m28.570s
sys     0m0.010s

为什么我的 Rust 代码比 C++ 变体慢四倍?我正在使用 Rust 1.12.0-beta.1。

我添加了时间测量:

let now = SystemTime::now();
line.clear();
stdin.read_line(&mut line).unwrap();
let set_len = line.trim_right().parse::<usize>().unwrap();
points_a.clear();
points_b.clear();
read_array(&mut stdin, set_len, &mut points_a);
read_array(&mut stdin, set_len, &mut points_b);
io_time += now.elapsed().unwrap();

let now = SystemTime::now();
let mut dist = u32::max_value();
for i in points_a.iter() {
    for j in points_b.iter() {
        dist = std::cmp::min(manhattan_dist(i, j), dist);
    }
}
calc_time += now.elapsed().unwrap();

writeln!(&amp;mut std::io::stderr(), "io_time: {}, calc_time: {}", io_time.as_secs(), calc_time.as_secs()).unwrap(); 打印io_time: 0, calc_time: 27

我每晚都试过rustc 1.13.0-nightly (e9bc1bac8 2016-08-24)

$ time ./test_rust < data.txt  > test3_res
io_time: 0, calc_time: 19

real    0m19.592s
user    0m19.560s
sys     0m0.020s
$ time ./test1 < data.txt  > test1_res

real    0m7.797s
user    0m7.780s
sys     0m0.010s

所以现在我的Core i7 相差 2.7 倍。

【问题讨论】:

  • 问题是你的实现没有一个是等价的。完全按照 C++ 版本编写 Rust 代码。在应用程序的开头处理 stdout 和 stdin 并锁定它们。直接写入标准输出的缓冲区,而不是使用会导致锁定+格式化开销的宏。
  • 尝试使用env RUSTFLAGS="-C target-cpu=native" cargo build --release 构建。 Rust 编译器拒绝使用各种高端 CPU 扩展而不专门启用它们。
  • FWIW,BufReader 不是stdin 的理想用法;尝试改用stdin.lock(),它会为您提供BufRead 并避免重复锁定。不过,这种差异并没有真正意义,因为这里的 IO 成本并不高。
  • @user1244932:我邀请您阅读 reddit 讨论 here,我个人觉得有许多 cmets 很有趣,而且有点太长,无法简明扼要地总结 :)

标签: rust


【解决方案1】:

区别当然是-march=native...。 Rust 通过-C target_cpu=native 实现了这一点,但这并没有带来同样的速度优势。这是因为LLVM 不愿意在这种情况下矢量化,而GCC 愿意。您可能会注意到,使用Clang(一个同样使用 LLVM 的 C++ 编译器)也会产生相对较慢的代码。

为了鼓励 LLVM 向量化,您可以将主循环移动到一个单独的函数中。或者,您可以使用本地块。如果你把代码写成

let dist = {
    let mut dist = i32::max_value();
    for &(a, b) in &points_a[..n] {
        for &(c, d) in &points_b[..n] {
            dist = std::cmp::min(((a - c).abs() + (b - d).abs()), dist);
        }
    }
    dist
} as u32;

Rust 和 C++ 之间的差异几乎可以忽略不计 (~4%)。

【讨论】:

    【解决方案2】:

    您在 C++ 中看到的绝大多数性能都归功于标志 -march=native

    这个标志不是 Rust 的 --release 的等效标志。它使用特定于编译它的 CPU 的 CPU 指令,因此特别是数学运算会方式更快。

    删除该标志会使 C++ 代码为 19 秒。

    C++ 代码中存在不安全性。不检查任何输入。 Rust 代码会检查它,你使用 .unwrap() - unwrap 有性能成本,有一个断言,然后是展开所需的代码等。

    使用if lets 而不是原始的unwraps,或在可能的情况下忽略结果,会使 Rust 代码再次崩溃。

    生锈:22 秒

    C++:19 秒

    3 秒从何而来?一点玩弄让我相信这是 println!printf,但我没有 C++ 代码的确切数字。我能说的是,当我在基准测试之外执行打印时,Rust 代码会下降到 13 秒。

    TLDR:您的编译器标志不同,您的 C++ 代码不安全。

    【讨论】:

    • -march=native 等价于-C target-cpu=native。这是我得到的时间:C++:12.417s; C++ 与-march=native: 9.005s;生锈:13.971s;使用-C target-cpu=native 生锈:11.943 秒。
    • 首先这不是我的c++ 代码,我提供了有问题的链接。
    • 第二,我怀疑您对 println!printf 的发现是否正确。我用O(N*log(N))而不是O(N * N)实现了这个算法的fast变体,但是留下输入/输出代码as is,现在结果0.7 seconds,类似于fast c++显示的内容。所以输入/输出不能超过0.1 seconds
    【解决方案3】:

    我绝对没有看到执行时间有任何差异。在我的机器上,

    C++:

    real    0m19.672s
    user    0m19.636s
    sys     0m0.060s
    

    生锈:

    real    0m19.047s
    user    0m19.028s
    sys     0m0.040s
    

    我用rustc -O test.rs -o ./test编译了Rust代码,用g++ -Ofast test.cpp -o test编译了C++代码。

    我正在运行带有 Linux 内核 4.6.3-040603-generic 的 Ubuntu 16.04。我运行它的笔记本电脑有 Intel(R) Core(TM) i5-6200U CPU 和 8GB RAM,没什么特别的。

    【讨论】:

    • 您使用哪些版本以及如何运行?我使用 rustc (1.12.0-beta.1) 和 gcc 5.3.0,并使用 time ./exe &lt; data &gt; /tmp/out 运行
    • rustc 1.13.0-nightly (e9bc1bac8 2016-08-24)
    • 另外我在计时测量之前运行cd /sys/devices/system/cpu/cpufreq/ &amp;&amp; for i in seq 0 7; do echo performance &gt; policy$i/scaling_governor; done,你也这样做了吗?
    • 和 g++ (Ubuntu 5.4.0-6ubuntu1~16.04.2) 5.4.0 20160609
    • 不,我没有惹恼我的调速器,但调速器会在几毫秒内做出反应,而基准测试需要几十秒。充其量,我预计调速器设置最多只能产生十分之几秒的差异。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-30
    • 2022-01-16
    • 2017-05-21
    • 2011-03-11
    相关资源
    最近更新 更多