将拥有的数组分割成拥有的两半。

8

我想将一个单独拥有的数组分成两个拥有的半部分——即两个独立的数组,而不是原始数组的切片。它们的大小是编译时常量。是否有一种方法可以做到这一点,而不必复制或克隆元素

let array: [u8; 4] = [0, 1, 2, 3];

let chunk_0: [u8; 2] = ???;
let chunk_1: [u8; 2] = ???;

assert_eq!(
  [0, 1],
  chunk_0
);
assert_eq!(
  [2, 3],
  chunk_1
);

由于这只会涉及到元素所有权的移动,我有一种预感,应该存在零成本抽象来实现此操作。我想知道是否可以通过巧妙使用 transmuteforget 来实现此操作。但是文档中对这些函数有很多可怕的警告。

我的主要动机是在内存中处理大型数组时减少内存复制的次数。例如:

let raw = [0u8; 1024 * 1024];

let a = u128::from_be_array(???); // Take the first 16 bytes
let b = u64::from_le_array(???); // Take the next 8 bytes
let c = ...

我所知道的实现上述模式的唯一方法是大量的内存复制,这是多余的。

3
https://docs.rs/array-tools/0.2.10/array_tools/fn.split.html - Daniel A. White
1
你想要达成什么目的?有没有理由不能直接使用 .split_atPlayground 示例 - Herohtar
1
@DanielA.White 那个箱子/函数 复制内存 - Matt Thomas
2
我已经编辑了问题,以使其更清晰,您想要的是数组而不是切片。但我很好奇,为什么不使用切片呢? - John Kugelman
2
如果您的最终目标是从字节数组中读取值,请考虑使用类似于ReadBytesExt的东西,它来自于byteorder包。它基本上会做你想要避免的事情,但这确实是你想要的,并且它可以进行优化。我应该指出,如果您有一个字节数组并且想要一个u128,那么这16个字节将在某个时候被复制。 - kmdreko
显示剩余8条评论
3个回答

8

您可以使用std::mem::transmute(警告:不安全!):

fn main() {
    let array: [u8; 4] = [0, 1, 2, 3];

    let [chunk_0, chunk_1]: [[u8; 2]; 2] =
        unsafe { std::mem::transmute::<[u8; 4], [[u8; 2]; 2]>(array) };

    assert_eq!([0, 1], chunk_0);
    assert_eq!([2, 3], chunk_1);
}

游乐场


6
use std::convert::TryInto;

let raw = [0u8; 1024 * 1024];
    
let a = u128::from_be_bytes(raw[..16].try_into().unwrap()); // Take the first 16 bytes
let b = u64::from_le_bytes(raw[16..24].try_into().unwrap()); // Take the next 8 bytes

实际上,我发现编译器在优化这方面很聪明。经过优化,它会在单次拷贝中完成以上操作(直接进入保存 ab 的寄存器)。例如,在 godbolt 上的代码展示如下:

use std::convert::TryInto;

pub fn cvt(bytes: [u8; 24]) -> (u128, u64) {
    let a = u128::from_be_bytes(bytes[..16].try_into().unwrap()); // Take the first 16 bytes
    let b = u64::from_le_bytes(bytes[16..24].try_into().unwrap()); // Take the next 8 bytes
    (a, b)
}

使用 -C opt-level=3 编译后的代码如下:
example::cvt:
        mov     rax, qword ptr [rdi + 8]
        bswap   rax
        mov     rdx, qword ptr [rdi]
        bswap   rdx
        mov     rcx, qword ptr [rdi + 16]
        ret

它优化掉了所有额外的副本,调用try_into方法,可能会发生恐慌等。


2
bytemuck 库提供了一种安全的包装器,用于重新解释任何“纯旧数据”数据类型(更精确地说:正确大小的所有可能字节序列都是有效值),只要输入和输出大小相同(或输入是一个字节长度可被输出类型大小整除的切片)。这相当于一个 transmute 解决方案,但无需编写任何新的 unsafe 代码。请注意,HTML 标签将保留不变。
let array: [u8; 4] = [0, 1, 2, 3];

let [chunk_0, chunk_1]: [[u8; 2]; 2] = bytemuck::cast(array);

如果你想避免使用额外的库,我建议采用已经发布的try_into()方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接