Я не могу воспроизвести тайминги, которые вы получаете. Вероятно, у вас есть ошибка в том, как вы измеряете (или у меня есть). На моей машине обе версии работают точно в одно и то же время.
В этом ответе я сначала сравню вывод сборки как с версией C++, так и с Rust. Впоследствии я опишу, как воспроизводить мои таймингов.
сравнение Сборка
Я сгенерировал код сборки с удивительной Compiler проводника (Rust code, C++ Code). Я скомпилировал код C++ с активированными оптимизациями (-O3
), чтобы сделать его честной (оптимизация компилятора на C++ не повлияла на измеренные тайминги). Вот в результате сборки (Rust слева, 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). Я не уверен в отношении всех дополнительных инструкций, которые 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
Затем я использовал этот Rust код для измерения все :
#![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, которое намного безопаснее (предотвращает перерасход указателей и последующие segfaults) и более интуитивно понятное, но добавляет дополнительные накладные расходы , – EvilTak
Вы знаете о 'std :: mem :: swap'? Кроме того, вы пытались использовать ['get_unchecked'] (https://doc.rust-lang.org/std/primitive.slice.html#method.get_unchecked), чтобы избежать индексов (в случае, если проверки границ не устранены)? Вы проверили, что потраченное время действительно в этом цикле? –
Я не могу воспроизвести время, которое вы испытываете. На моей машине код Rust выполняется примерно через 30 мкс, как это делает код на C++. (Редактирование: я решил написать ответ об этом) –