取得しているタイミングを再現できません。あなたは、あなたが測定する方法(または私が持っている方法)におそらく間違いがあります。私のマシンでは、両方のバージョンがまったく同じ時間に実行されます。
この回答では、まずC++とRustの両方のアセンブリ出力を比較します。その後、私は私ののタイミングを再現する方法を説明します。
組立比較
私は素晴らしいコンパイラエクスプローラ(Rust code、C++ Code)とのアセンブリコードを生成しました。私は最適化を有効にしてC++コードをコンパイルしました(-O3
)。これは公正なゲームです(C++コンパイラの最適化は測定タイミングに影響を与えませんでした)。あなたはすぐに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++)はあります。私はRustが作成する追加の命令のすべてについてはわかりませんが、少なくとも半分はバインドチェックのためのものです。しかし、この境界チェックは、私が理解する限り、[]
経由の実際のアクセスではなく、ループの繰り返しごとに1回だけです。これは、スライスの長さが4で割り切れない場合にのみ当てはまります。しかし、Rustアセンブリはより良い(バインドされたチェックであっても)ことができると思います。
コメントに記載されているとおり、get_unchecked()
とget_unchecked_mut()
を使用すると、バインドされたチェックを削除できます。しかし、これは私の測定のパフォーマンスには影響しませんでした。
最後に[&]::swap(i, j)
を使用してください。
for chunk in imagebuf.chunks_mut(4) {
chunk.swap(0, 2);
}
これは、パフォーマンスに特に影響しませんでした。しかし、それはより短く、より良いコードです。
を測定
私は(foocpp.cpp
で)このC++コードを使用:その後、私はすべてを測定するために、この錆コードを使用
gcc -c -O3 foocpp.cpp && ar rvs libfoocpp.a foocpp.o
:私はそれをコンパイル
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;
}
}
を:
#![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によって生成された追加のアセンブリが遅い実行を引き起こさないのか正確にはわかりません。
現在の 'Iterator'ソリューションを使用する代わりに、ポインタで安全でないコード(本質的にはC++コード)を使用し、より安全で(ポインタのオーバーランや結果的なセグメンテーションを防ぐ)、より直感的ですがオーバーヘッドが増えます。 – EvilTak
'std :: mem :: swap'について知っていますか?また、インデックスを避けるために['get_unchecked'](https://doc.rust-lang.org/std/primitive.slice.html#method.get_unchecked)を試してみましたか(境界チェックを省略しない場合)?本当にこのループに費やされた時間を確認しましたか? –
体験した時間を再現することはできません。私のマシンでは、RustコードはC++コードとまったく同じように約30μsで実行されます。 (編集:私はこれについての答えを書くことにしました) –