2016-08-27 86 views
15

我读Minimal distance in Manhattan metric并在Rust中重写了作者的“幼稚”实现。 C++的变体是:这个Rust程序为什么这么慢?我错过了什么?

#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); 
    } 
} 

锈病变体是:

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 

为什么我的锈的代码比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!(&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倍的差别。

+0

这个问题是肯定的,你的任何实现都是等价的。完全按照C++版本编写Rust代码。在应用程序的开头处理stdout和stdin,并锁定它们。直接写入标准输出缓冲区,而不是使用导致锁定+格式化开销的宏。 – mmstick

+1

尝试使用'env RUSTFLAGS =“ - C target-cpu = native”cargo build --release'构建。 Rust编译器拒绝使用各种高端CPU扩展,而没有明确地启用它们。 – mmstick

+1

FWIW,'BufReader'不是'stdin'的理想用法;尝试使用'stdin.lock()'代替,它为您提供'BufRead'并避免重复锁定。不过,这种差异并不真正有意义,因为IO在这里并不是一个很大的成本。 – Veedrac

回答

38

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

为了鼓励LLVM矢量化,可以将主循环移动到单独的函数中。或者,您可以使用本地块。如果你仔细地写代码,那么Rust和C++之间的区别几乎可以忽略不计(〜4%)。

2

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

C++:

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

防锈:

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

rustc -O test.rs -o ./testg++ -Ofast test.cpp -o test的C++代码编译的代码锈。

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

+0

您使用了哪些版本以及如何运行?我使用rustc(1.12.0-beta.1)和gcc 5.3.0,并运行'time ./exe​​/tmp/out' – user1244932

+0

rustc 1.13.0-nightly(e9bc1bac8 2016-08-24) – coder543

+0

另外I在'seq 0 7'中运行'cd/sys/devices/system/cpu/cpufreq/&& for i';做回声性能>政策$ i/scaling_governor;在计时测量之前完成,你是否也这样做? – user1244932

25

你在C++中看到的绝大多数性能是由于国旗-march=native造成的。

此标志不是Rust的--release的等效标志。它使用专门针对CPU编译的CPU指令,所以数学尤其是方式更快。

删除该标记将C++代码置于19秒。

然后在C++代码中存在不安全的现象。没有任何输入被检查。锈病代码不检查它,你用.unwrap() - unwrap有性能上的成本,有一个说法,那么需要平仓的代码等

使用if let s,而不是原始unwrap S,或忽略结果,其中,从而带来Rust代码再次下降。

锈22秒

C++:19秒

哪里的来自的3秒?有一点玩耍让我相信它是println!printf,但我没有硬编码的C++代码。我能说的是,当我在基准测试之外执行打印时,Rust代码降至13秒。

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

+0

'-march = native'相当于'-C target-cpu = native'。这是我得到的时间:C++:12.417s;带有'-march = native'的C++:9.005s;锈:13.971s;铁锈与'-C目标cpu =本地':11.943s。 –

+0

首先,它不是我的'C++'代码,我提供了来自哪里的链接。 – user1244932

+0

第二我怀疑你对'println!'和'printf'的发现是正确的。我用'O(N * log(N))'而不是'O(N * N)'实现了这个算法的'fast'变体,但是保持原样输入/输出代码,现在结果为'0.7秒'类似于'fast C++'显示的内容。所以输入/输出不能超过0.1秒 – user1244932