2016-08-27 87 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