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

时间:2020-02-12 02:13:59

标签: rust

我需要分配一个缓冲区来读取File,但是该缓冲区必须与高速缓存行的大小(64字节)对齐。我正在为Vec寻找类似这样的函数:

pub fn with_capacity_and_aligned(capacity: usize, alignment: u8) -> Vec<T>

这将给我所需的64字节对齐方式。这显然不存在,但是可能存在一些我不知道的等效项(即“ hacks”)。

因此,当我使用此功能(它将给我所需的对齐方式)时,我可以安全地编写此代码:

#[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>())]) }
        };
    }
}

并且由于对齐问题(like launching an instruction)而没有发生任何恐慌。

2 个答案:

答案 0 :(得分:4)

您可以使用#[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>(),
    )
}

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

出于同样的原因,该函数为unsafe。删除类型时,the memory must be back to its original allocation,但此功能无法控制。

感谢corrections and additions的BurntSushi5。

另请参阅:

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

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


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

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(())
}

另请参阅:

答案 1 :(得分:-2)

长度为64字节倍数的Vec<u8>是否已经对齐?

此外,这是对齐某些未对齐数据(也许是[u8; 64] ???)的一种方法:

#[repr(C, align(64))]
pub struct Padded<T>(T);

#[repr(C, packed)]
pub struct NotAligned {
    num: i32,
    flag: bool,
    byte: u8,
}

fn main() {
    println!("{}; {}", mem::align_of::<NotAligned>(), mem::size_of::<NotAligned>());
    println!("{}; {}", mem::align_of::<Padded<NotAligned>>(), mem::size_of::<Padded<NotAligned>>());

    let aligned_data: Vec<Padded<NotAligned>> = Vec::with_capacity(42);
}