Мне нужно преобразовать первые 8 байтов строки в Rust в u64 с прямым порядком байтов. Этот код почти работает:
fn main() {
let s = String::from("01234567");
let mut buf = [0u8; 8];
buf.copy_from_slice(s.as_bytes());
let num = u64::from_be_bytes(buf);
println!("{:X}", num);
}
Есть несколько проблем с этим кодом. Во-первых, это работает, только если длина строки ровно 8 байт. .copy_from_slice()
требует, чтобы и источник, и место назначения имели одинаковую длину. С этим легко справиться, если строка слишком длинная, потому что я могу просто взять кусок нужной длины, но если строка короткая, это не сработает.
Другая проблема заключается в том, что этот код является частью функции, которая зависит от производительности очень. Он работает в узком цикле над большим набором данных.
В C я бы просто обнулил buf, memcpy поверх нужного количества байтов и выполнил приведение к unsigned long.
Есть ли способ сделать это в Rust, который работает так же быстро?
Вы можете просто изменить существующий код, чтобы учесть длину при копировании:
let len = 8.min(s.len());
buf[..len].copy_from_slice(&s.as_bytes()[..len]);
Если строка короткая, это, конечно, скопирует байты в то, что станет старшими битами u64
.
Что касается производительности: в этом простом тесте main()
преобразования полностью оптимизированы, чтобы стать постоянным целым числом. Итак, нам нужна явная функция или цикл:
pub fn convert(s: &str) -> u64 {
let mut buf = [0u8; 8];
let len = 8.min(s.len());
buf[..len].copy_from_slice(&s.as_bytes()[..len]);
u64::from_be_bytes(buf)
}
Это (на Rust Playground) генерирует сборку:
playground::convert:
pushq %rax
movq %rdi, %rax
movq $0, (%rsp)
cmpq $8, %rsi
movl $8, %edx
cmovbq %rsi, %rdx
movq %rsp, %rdi
movq %rax, %rsi
callq *memcpy@GOTPCREL(%rip)
movq (%rsp), %rax
bswapq %rax
popq %rcx
retq
Я немного скептически отношусь к тому, что этот вызов memcpy
на самом деле является хорошей идеей по сравнению с простой выдачей инструкций для копирования байтов, но я не эксперт по производительности на уровне инструкций, и, предположительно, он будет по крайней мере эквивалентен вашему коду C, явно вызывающему memcpy()
. Что мы действительно видим, так это то, что есть нет ветвей в скомпилированном коде, только условное перемещение, предположительно для обработки выбора 8
против len()
— и никакой паники проверки границ.
(И сгенерированная сборка, конечно же, будет другой — надеюсь, к лучшему — когда эта функция или фрагмент кода будет встроена в более крупный цикл.)
Что не так с вашим подходом «в C»? Похоже, вы очень близки к тому, чтобы сделать то же самое в Rust. У вас есть обнуленный буфер, вы (почти) получили часть
memcpy
, аu64::from_be_bytes
— это актерский состав, который вы ищете. Похоже, у вас есть хорошая идея для алгоритма и 90% выполненной работы. Осталось только проверить размер.