2017-02-20 67 views
3

我有以下的代碼:加快循環

for chunk in imagebuf.chunks_mut(4) { 
    let temp = chunk[0]; 
    chunk[0] = chunk[2]; 
    chunk[2] = temp; 
} 

對於40000 u8秒的陣列,它需要我的機器,使用cargo build --release編上約2.5毫秒。

下面的C++代碼需要大約100我們完全一樣的數據(通過實施並使用FFI從生鏽稱之爲驗證):

for(;imagebuf!=endbuf;imagebuf+=4) { 
    char c=imagebuf[0]; 
    imagebuf[0]=imagebuf[2]; 
    imagebuf[2]=c; 
} 

我想它應該有可能加速Rust實現的速度與C++版本一樣快。

Rust程序使用cargo --release構建,C++程序的構建沒有任何優化標誌。

任何提示?

+1

您可能希望查看使用指針(本質上是C++代碼)的不安全代碼,而不是使用當前的Iterator解決方案,該解決方案更安全(防止指針溢出和隨後的段錯誤),並且更直觀,但增加了更多開銷。 – EvilTak

+1

你知道'std :: mem :: swap'嗎?另外,你是否嘗試過使用['get_unchecked'](https://doc.rust-lang.org/std/primitive.slice.html#method.get_unchecked)來避免索引(如果邊界檢查沒有被忽略)?你有沒有檢查花在這個循環上的時間是真的嗎? –

+1

我無法重現您體驗的時間。在我的機器上,Rust代碼在大約30μs內執行,與C++代碼完全相同。 (編輯:我決定寫一個關於這個問題的答案) –

回答

7

我無法重現您獲得的時間。你可能在測量方式上有錯誤(或者我有)。在我的機器上,兩個版本在完全相同的時間運行。

在這個答案中,我將首先比較C++和Rust版本的彙編輸出。之後我會介紹如何重現我的時間。


大會比較

我生成具有驚人的編譯器資源管理器(Rust codeC++ Code)的彙編代碼。我編譯了C++代碼,激活了優化(-O3),以使它成爲一個公平的遊戲(儘管C++編譯器優化對測量的時序沒有影響)。這裏所得到的組件(防鏽左,C++右):

example::foo_rust:     | foo_cpp(char*, char*): 
    test rsi, rsi     |  cmp  rdi, rsi 
    je  .LBB0_5     |  je  .L3 
    mov  r8d, 4     | 
.LBB0_2:        | .L5: 
    cmp  rsi, 4     | 
    mov  rdx, rsi     | 
    cmova rdx, r8     | 
    test rdi, rdi     | 
    je  .LBB0_5     | 
    cmp  rdx, 3     | 
    jb  .LBB0_6     | 
    movzx ecx, byte ptr [rdi]  |  movzx edx, BYTE PTR [rdi] 
    movzx eax, byte ptr [rdi + 2] |  movzx eax, BYTE PTR [rdi+2] 
             |  add  rdi, 4 
    mov  byte ptr [rdi], al  |  mov  BYTE PTR [rdi-2], al 
    mov  byte ptr [rdi + 2], cl |  mov  BYTE PTR [rdi-4], dl 
    lea  rdi, [rdi + rdx]   | 
    sub  rsi, rdx     |  cmp  rsi, rdi 
    jne  .LBB0_2     |  jne  .L5 
.LBB0_5:        | .L3: 
             |  xor  eax, eax 
    ret        |  ret 
.LBB0_6:        | 
    push rbp      +-----------------+ 
    mov  rbp, rsp         | 
    lea  rdi, [rip + panic_bounds_check_loc.3]  | 
    mov  esi, 2          | 
    call core::panicking::[email protected]  | 

您可以立即看到C++事實上確實產生少了很多組件(無生產幾乎同樣多的指令防鏽做優化C++)。我不確定Rust生產的所有附加指令,但其中至少有一半是用於綁定檢查的。但據我瞭解,這種綁定檢查並不是通過[]進行實際訪問,而是每循環迭代一次。這是爲了切片的長度不能被4整除的情況。但是我認爲Rust裝配可能會更好(即使在進行檢查時也是如此)。

正如評論中所述,您可以使用get_unchecked()get_unchecked_mut()刪除綁定檢查。但請注意,這並不影響我測量的性能!

最後:你應該在這裏使用[&]::swap(i, j)

for chunk in imagebuf.chunks_mut(4) { 
    chunk.swap(0, 2); 
} 

這同樣不會顯着影響性能。但它更短,更好的代碼。


測量

我用這個C++代碼(在foocpp.cpp):

extern "C" void foo_cpp(char *imagebuf, char *endbuf); 

void foo_cpp(char* imagebuf, char* endbuf) { 
    for(;imagebuf!=endbuf;imagebuf+=4) { 
     char c=imagebuf[0]; 
     imagebuf[0]=imagebuf[2]; 
     imagebuf[2]=c; 
    } 
} 

予編譯它:

gcc -c -O3 foocpp.cpp && ar rvs libfoocpp.a foocpp.o 

然後我用這個鏽代碼來衡量一切:

#![feature(test)] 

extern crate libc; 
extern crate test; 

use test::black_box; 
use std::time::Instant; 

#[link(name = "foocpp")] 
extern { 
    fn foo_cpp(start: *mut libc::c_char, end: *const libc::c_char); 
} 

pub fn foo_rust(imagebuf: &mut [u8]) { 
    for chunk in imagebuf.chunks_mut(4) { 
     let temp = chunk[0]; 
     chunk[0] = chunk[2]; 
     chunk[2] = temp; 
    } 
} 

fn main() { 
    let mut buf = [0u8; 40_000]; 

    let before = Instant::now(); 

    foo_rust(black_box(&mut buf)); 
    black_box(buf); 

    println!("rust: {:?}", Instant::now() - before); 

    // ---------------------------------- 

    let mut buf = [0u8 as libc::c_char; 40_000]; 

    let before = Instant::now(); 

    let ptr = buf.as_mut_ptr(); 
    let end = unsafe { ptr.offset(buf.len() as isize) }; 
    unsafe { foo_cpp(black_box(ptr), black_box(end)); } 
    black_box(buf); 

    println!("cpp: {:?}", Instant::now() - before); 
} 

遍佈整個地方阻止編譯器優化它不應該在的位置。我與(每晚編譯器)執行它:

LIBRARY_PATH=.:$LIBRARY_PATH cargo run --release 

給予我(i7-6700HQ)值這樣的:

rust: Duration { secs: 0, nanos: 30583 } 
cpp: Duration { secs: 0, nanos: 30810 } 

的時間波動很多(方式比這兩個版本之間的差異更)。我不確定爲什麼由Rust生成的附加程序集不會導致較慢的執行。

+0

男人,驚人的答案!我運行了你的代碼(Intel Xeon E3-1575M v5),並得到: rust:duration {secs:0,nanos:21839} cpp:Duration {secs:0,nanos:6576} (有一些變化) –

+0

改變執行順序,所以cpp代碼先行,似乎稍微改變了結果。 C版本的平均速度似乎快了一倍。 –

+0

沒有辦法接近我第一次得到的差異。我不願意承認,可能是因爲我意外地運行了錯誤的二進制文件(未優化的二進制文件)。 非常感謝您的詳細調查!在學習Rust的時候,我真的體會到了這一點,這是Rust社區裏非常愉快的語調。謝謝! –