如何分配与缓存行大小对齐的 Vec<u8>?

Nul*_*lik 2 rust

我需要分配一个缓冲区来读取 a File,但是这个缓冲区必须与缓存行的大小(64 字节)对齐。我正在寻找一个有点像这样的功能Vec:

pub fn with_capacity_and_aligned(capacity: usize, alignment: u8) -> Vec<T>
Run Code Online (Sandbox Code Playgroud)

这将为我提供所需的 64 字节对齐。这显然不存在,但可能有一些我不知道的等价物(即“黑客”)。

所以,当我使用这个函数(它会给我所需的对齐)时,我可以安全地编写这段代码:

#[repr(C)]
struct Header {
    magic: u32,
    some_data1: u32,
    some_data2: u64,
}

let cache_line_size = 64; // bytes
let buffer: Vec<u8> = Vec::<u8>::with_capacity_and_alignment(some_size, cache_line_size);
match file.read_to_end(&mut buffer) {
    Ok(_) => {
        let header: Header = {
            // and since the buffer is aligned to 64 bytes, I wont get any SEGFAULT
            unsafe { transmute(buffer[0..(size_of::<Header>())]) }
        };
    }
}
Run Code Online (Sandbox Code Playgroud)

并且不会因为对齐问题(例如启动指令)而出现任何恐慌。

She*_*ter 5

您可以使用 强制将类型对齐到特定大小#[repr(align(...))]。我们还用于repr(C)确保此类型具有与字节数组相同的内存布局。

然后,您可以创建对齐类型的向量并将其转换为适当类型的向量:

use std::mem;

#[repr(C, align(64))]
struct AlignToSixtyFour([u8; 64]);

unsafe fn aligned_vec(n_bytes: usize) -> Vec<u8> {
    // Lazy math to ensure we always have enough.
    let n_units = (n_bytes / mem::size_of::<AlignToSixtyFour>()) + 1;

    let mut aligned: Vec<AlignToSixtyFour> = Vec::with_capacity(n_units);

    let ptr = aligned.as_mut_ptr();
    let len_units = aligned.len();
    let cap_units = aligned.capacity();

    mem::forget(aligned);

    Vec::from_raw_parts(
        ptr as *mut u8,
        len_units * mem::size_of::<AlignToSixtyFour>(),
        cap_units * mem::size_of::<AlignToSixtyFour>(),
    )
}
Run Code Online (Sandbox Code Playgroud)

Vec<u8>如果您重新分配数据,则无法保证将保持对齐。这意味着您无法重新分配,因此您需要预先知道分配多少。

该功能是unsafe出于同样的原因。当类型被删除时,内存必须回到它原来的分配状态,但是这个函数不能控制它。

感谢 BurntSushi5 的更正和补充。

也可以看看:

由于上述限制和不安全性,另一个潜在的想法是分配一个足够大的缓冲区(可能有一些摆动空间),然后使用它align_to来获得一个正确对齐的块。您可以使用与AlignToSixtyFour上述相同的类型,然后将其&[AlignToSixtyFour]转换为&[u8]具有类似逻辑的 a。

此技术可用于给出对齐的(可选可变的)切片。由于它们是切片,因此您不必担心用户重新分配或删除它们。这将允许您将其包装成更好的类型。


话虽如此,我认为这里依赖对齐方式不适合您从文件中读取结构的实际目标。只需读取字节 ( u32, u32, u64) 并构建结构:

use byteorder::{LittleEndian, ReadBytesExt}; // 1.3.4
use std::{fs::File, io};

#[derive(Debug)]
struct Header {
    magic: u32,
    some_data1: u32,
    some_data2: u64,
}

impl Header {
    fn from_reader(mut reader: impl io::Read) -> Result<Self, Box<dyn std::error::Error>> {
        let magic = reader.read_u32::<LittleEndian>()?;
        let some_data1 = reader.read_u32::<LittleEndian>()?;
        let some_data2 = reader.read_u64::<LittleEndian>()?;

        Ok(Self {
            magic,
            some_data1,
            some_data2,
        })
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut f = File::open("/etc/hosts")?;

    let header = Header::from_reader(&mut f)?;

    println!("{:?}", header);

    Ok(())
}
Run Code Online (Sandbox Code Playgroud)

也可以看看:

  • 很好的答案,但我认为有两个问题。首先,我相信“AlignToSixtyFour”需要是“repr(transparent)”(或“repr(C)”),否则不能保证与“[u8;”共享相同的内存布局。64]`。其次,我认为你的“aligned_vec”函数应该标记为“不安全”。你一针见血地指出重新分配是危险的,但释放内存也很危险。UB 释放与分配的对齐方式不同的分配。例如,请参阅 https://doc.rust-lang.org/std/alloc/trait.GlobalAlloc.html#tymethod.dealloc (2认同)

归档时间:

查看次数:

1912 次

最近记录:

6 年,7 月 前