Как мне выделить Ve c что выровнено по размеру строки кэша? - PullRequest
1 голос
/ 12 февраля 2020

Мне нужно выделить буфер для чтения из File, но этот буфер должен быть выровнен по размеру строки кэша (64 байта). Я ищу функцию, похожую на эту для Vec:

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

, которая дала бы мне 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>())]) }
        };
    }
}

и не получать никакой паники из-за проблем с выравниванием (, таких как запуск инструкции ).

Ответы [ 2 ]

2 голосов
/ 12 февраля 2020

Вы можете принудительно настроить выравнивание типа по определенному размеру, используя #[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. Когда тип отброшен, память должна вернуться к своему первоначальному распределению , но эта функция не может это контролировать.

Благодаря BurntSushi5 для исправлений и дополнений .

См. Также:

Из-за ограничений и небезопасности, приведенных выше, другой потенциальной идеей может быть выделение достаточно большого буфера (возможно, с некоторым пространством для маневра), а затем использование align_to, чтобы получить правильно выровненный фрагмент. Вы можете использовать тот же тип AlignToSixtyFour, что и выше, а затем преобразовать &[AlignToSixtyFour] в &[u8] с аналогичными логинами c.

Этот метод может использоваться для выдачи (необязательно изменяемых) слайсов которые выровнены. Поскольку они представляют собой кусочки, вам не нужно беспокоиться о том, что пользователь перераспределяет или удаляет их. Это позволит вам обернуть его в более приятный тип.


Несмотря на это, я думаю, что полагаться на выравнивание здесь неуместно для вашей реальной цели чтения структуры из файла. Просто прочитайте байты (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(())
}

См. Также:

0 голосов
/ 12 февраля 2020

Не будет ли выровнен какой-либо Vec<u8>, имеющий длину, кратную 64 байтам?

Кроме того, вот один из способов, которым вы могли бы выровнять некоторые невыровненные данные (возможно [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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...