以下函数的目的是加快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架构(可能还有其他架构)不会运行优化版本?
2条答案
按热度按时间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属性(特别是涉及到性能配置文件时)。w41d8nur2#
虽然没有解决最初的问题,但我能够通过根本不使用切片来满足我的需求,而没有不安全的代码:
这些生成高效的汇编代码。
x86_64:
第64章:
更新:感谢Chayim Friedman,我现在有了下面的代码,它不依赖于夜间特性,但编译成相同的单一汇编指令。