rust 如何编写只为能够执行非对齐读取的平台编译的代码?

jk9hmnmh  于 2023-02-08  发布在  其他
关注(0)|答案(2)|浏览(99)

以下函数的目的是加快u64从切片的读取速度(可能是未对齐的)。
优化后的函数在x86_64上编译为mov rax, qword ptr [rdi],在aarch 64上编译为ldr x0, [x0](未优化的版本(在小端优先平台上使用时)编译为相同的程序集,但在-O3处内联时通常会扩展为16条以上的指令)。
此代码尚不正确(请参见FIXME):

// Unoptimised version, suitable for both endianesses and any lack of unaligned reads.
#[cfg(target_endian="big")]
fn u64_from_slice(slice: &[u8]) -> u64 {
    debug_assert!(slice.len() >= size_of::<u64>());
    unsafe {
        *slice.get_unchecked(0) as u64 | 
        ((*slice.get_unchecked(1) as u64) << 8) |
        ((*slice.get_unchecked(2) as u64) << 16) |
        ((*slice.get_unchecked(3) as u64) << 24) |
        ((*slice.get_unchecked(4) as u64) << 32) |
        ((*slice.get_unchecked(5) as u64) << 40) |
        ((*slice.get_unchecked(6) as u64) << 48) |
        ((*slice.get_unchecked(7) as u64) << 56)
    }
}

// FIXME: This is only valid on architectures which can perform unaligned reads.
#[cfg(target_endian="little")]
pub fn u64_from_slice(slice: &[u8]) -> u64 {
    debug_assert!(slice.len() >= size_of::<u64>());
    unsafe {
        let r = &*(slice as *const [u8] as *const [u8; size_of::<u64>()]);
        *mem::transmute::<&[u8; size_of::<u64>()], &u64>(r)
    }
}

很多年前,我曾在ARM架构上工作过,在该架构中,未对齐读取会导致对齐读取,然后重新排列字节,以便将该地址的u8或u16移到寄存器的最低位。
在这种情况下,我的target_endian="little"不足以使上面的代码正确。
如何确保那些ARM架构(可能还有其他架构)不会运行优化版本?

gudnpqoy

gudnpqoy1#

优化后的函数在x86_64上编译为mov rax, qword ptr [rdi],在aarch64上编译为ldr x0, [x0]
from_le_bytes非常接近时,这真的是一个有用的增益吗?失败分支中的unreachable_unchecked()基本上可以让您到达那里?两者都保留的唯一内容是一个分支的切片大小,但这应该是一个非常好的预测分支。
很多年前,我曾在ARM架构上工作过,在该架构中,未对齐读取会导致对齐读取,然后重新排列字节,以便将该地址的u8或u16移到寄存器的最低位。
您可能会想到ARMv6和更早的版本,特别是ARMv5和更低版本,它会将地址舍入为4的倍数,然后可能会进行奇怪的旋转。
AVMv8很好地支持未对齐的读取,至少对于大多数操作是这样,尽管可能会出现perf命中。
如何确保那些ARM架构(可能还有其他架构)不会运行优化版本?
我认为使用target_arch显式枚举是最不坏的选择,它可能会将perfs留在桌面上,因为处理未对齐的读取并不总是ISA属性(特别是涉及到性能配置文件时)。

w41d8nur

w41d8nur2#

虽然没有解决最初的问题,但我能够通过根本不使用切片来满足我的需求,而没有不安全的代码:

#![feature(slice_as_chunks)]
...
pub fn u64_from_first_eight(buf: &[u8; 9]) -> u64 {
    let parts: (&[[u8; 8]], &[u8]) = buf.as_chunks();
    u64::from_le_bytes(parts.0[0])
}

pub fn u64_from_last_eight(buf: &[u8; 9]) -> u64 {
    let parts: (&[u8], &[[u8; 8]]) = buf.as_rchunks();
    u64::from_le_bytes(parts.1[0])
}

这些生成高效的汇编代码。
x86_64:

example::u64_from_first_eight:
        mov     rax, qword ptr [rdi]
        ret

example::u64_from_last_eight:
        mov     rax, qword ptr [rdi + 1]
        ret

第64章:

example::u64_from_first_eight:
        ldr     x0, [x0]
        ret

example::u64_from_last_eight:
        ldur    x0, [x0, #1]
        ret

更新:感谢Chayim Friedman,我现在有了下面的代码,它不依赖于夜间特性,但编译成相同的单一汇编指令。

pub fn u64_from_low_eight(buf: &[u8; 9]) -> u64 {
    let bytes: &[u8; size_of::<u64>()] = buf[..size_of::<u64>()].try_into().unwrap();
    u64::from_le_bytes(*bytes)
}

pub fn u64_from_high_eight(buf: &[u8; 9]) -> u64 {
    let bytes: &[u8; size_of::<u64>()] = buf[1..(size_of::<u64>()+1)].try_into().unwrap();
    u64::from_le_bytes(*bytes)
}

相关问题