Преобразовать байты в u64

Мне нужно преобразовать первые 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, который работает так же быстро?

Что не так с вашим подходом «в C»? Похоже, вы очень близки к тому, чтобы сделать то же самое в Rust. У вас есть обнуленный буфер, вы (почти) получили часть memcpy, а u64::from_be_bytes — это актерский состав, который вы ищете. Похоже, у вас есть хорошая идея для алгоритма и 90% выполненной работы. Осталось только проверить размер.

Silvio Mayolo 20.03.2022 00:28
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
1
98
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы можете просто изменить существующий код, чтобы учесть длину при копировании:

    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() — и никакой паники проверки границ.

(И сгенерированная сборка, конечно же, будет другой — надеюсь, к лучшему — когда эта функция или фрагмент кода будет встроена в более крупный цикл.)

Другие вопросы по теме