我無法重現您獲得的時間。你可能在測量方式上有錯誤(或者我有)。在我的機器上,兩個版本在完全相同的時間運行。
在這個答案中,我將首先比較C++和Rust版本的彙編輸出。之後我會介紹如何重現我的時間。
大會比較
我生成具有驚人的編譯器資源管理器(Rust code,C++ 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生成的附加程序集不會導致較慢的執行。
您可能希望查看使用指針(本質上是C++代碼)的不安全代碼,而不是使用當前的Iterator解決方案,該解決方案更安全(防止指針溢出和隨後的段錯誤),並且更直觀,但增加了更多開銷。 – EvilTak
你知道'std :: mem :: swap'嗎?另外,你是否嘗試過使用['get_unchecked'](https://doc.rust-lang.org/std/primitive.slice.html#method.get_unchecked)來避免索引(如果邊界檢查沒有被忽略)?你有沒有檢查花在這個循環上的時間是真的嗎? –
我無法重現您體驗的時間。在我的機器上,Rust代碼在大約30μs內執行,與C++代碼完全相同。 (編輯:我決定寫一個關於這個問題的答案) –