rust 以< u32>< u8>最小的开销就地将Vec转换为Vec

vxqlmq5t  于 2023-01-05  发布在  其他
关注(0)|答案(4)|浏览(349)

我正在尝试将u32Vec转换为u8Vec,最好是就地转换,并且没有太多开销。
我目前的解决方案依赖于不安全的代码来重新构造Vec。是否有更好的方法来实现这一点?与我的解决方案相关的风险是什么?

use std::mem;
use std::vec::Vec;

fn main() {
    let mut vec32 = vec![1u32, 2];
    let vec8;
    unsafe {
        let length = vec32.len() * 4; // size of u8 = 4 * size of u32
        let capacity = vec32.capacity() * 4; // ^
        let mutptr = vec32.as_mut_ptr() as *mut u8;
        mem::forget(vec32); // don't run the destructor for vec32

        // construct new vec
        vec8 = Vec::from_raw_parts(mutptr, length, capacity);
    }

    println!("{:?}", vec8)
}

Rust Playground link

0pizxfdo

0pizxfdo1#

1.每当编写unsafe代码块时,我 * 强烈 * 鼓励人们在代码块上添加注解,解释 * 为什么你认为代码实际上是安全的 *。这种类型的信息对将来阅读代码的人很有用。
1.不要添加关于"幻数" 4的注解,而直接使用mem::size_of::<u32>,我甚至会使用size_of表示u8,并执行除法以获得最大的清晰度。
1.您可以从unsafe块返回新创建的Vec。
1.正如评论中提到的,像这样"转储"一块数据会使数据格式 * 依赖于平台 *;在小端和大端系统中,你会得到不同的答案。这可能会在将来导致大量的调试难题。文件格式要么将平台端编码到文件中(使读取者的工作更困难),要么只将特定的端写入文件(使写入者的工作更困难)。
1.我可能会将整个unsafe块移动到一个函数中,并给它一个名称,只是为了组织的目的。
1.您不需要导入Vec,它在prelude中。

use std::mem;

fn main() {
    let mut vec32 = vec![1u32, 2];

    // I copy-pasted this code from StackOverflow without reading the answer 
    // surrounding it that told me to write a comment explaining why this code 
    // is actually safe for my own use case.
    let vec8 = unsafe {
        let ratio = mem::size_of::<u32>() / mem::size_of::<u8>();

        let length = vec32.len() * ratio;
        let capacity = vec32.capacity() * ratio;
        let ptr = vec32.as_mut_ptr() as *mut u8;

        // Don't run the destructor for vec32
        mem::forget(vec32);

        // Construct new Vec
        Vec::from_raw_parts(ptr, length, capacity)
    };

    println!("{:?}", vec8)
}

Playground
我对这段代码最大的未知担忧在于与Vec关联的内存的对齐。
Rust的底层分配器使用 * 特定的Layout * 分配和释放内存。Layout包含指针的 * 大小 * 和 * 对齐方式 * 等信息。
我假设这段代码需要Layout来匹配对allocdealloc的成对调用,如果是这种情况,删除从Vec<u32>构造的Vec<u8>可能会告诉分配器错误的对齐,因为该信息基于元素类型。
如果没有更好的知识,"最好"的做法就是让Vec<u32>保持原样,而只是向它添加一个&[u8],切片不与分配器交互,从而避免了这个问题。
即使不与分配器交互,您也需要小心对齐!
另见:

beq87vna

beq87vna2#

如果就地转换不是强制性的,那么类似下面这样的代码可以管理bytes order控制并避免不安全的代码块:

extern crate byteorder;

use byteorder::{WriteBytesExt, BigEndian};

fn main() {
    let vec32: Vec<u32> = vec![0xaabbccdd, 2];
    let mut vec8: Vec<u8> = vec![];

    for elem in vec32 {
        vec8.write_u32::<BigEndian>(elem).unwrap();
    }

    println!("{:?}", vec8);
}
t3psigkw

t3psigkw3#

要正确地进行这种转换,需要在调用from_raw_parts之前,检查Vec的关联分配器并调用shrink将布局转换为新的对齐方式,这取决于分配器是否能够执行就地重新分配。
如果不需要调整结果向量的大小,那么将vec的&mut [u32]借位重新解释为&mut [u8]将是一个更简单的选择。

w6lpcovy

w6lpcovy4#

这就是我如何解决这个问题使用一个位移位副本。
它可以在我的x64机器上工作,但我不确定我是否对小/大endianism做了不安全的假设。
如果可以在不需要副本的情况下在内存中执行此强制转换,则运行时性能会更快,但我还没有想出如何执行此操作。

/// Cast Vec<u32> to Vec<u8> without modifying underlying byte data
/// ```
/// # use fractals::services::vectors::vec_u32_to_u8;
/// assert_eq!( vec_u32_to_u8(&vec![ 0x12345678 ]), vec![ 0x12u8, 0x34u8, 0x56u8, 0x78u8 ]);
/// ```
#[allow(clippy::identity_op)]
pub fn vec_u32_to_u8(data: &Vec<u32>) -> Vec<u8> {
    // TODO: https://stackoverflow.com/questions/72631065/how-to-convert-a-u32-array-to-a-u8-array-in-place
    // TODO: https://stackoverflow.com/questions/29037033/how-to-slice-a-large-veci32-as-u8
    let capacity = 32/8 * data.len() as usize;  // 32/8 == 4
    let mut output = Vec::<u8>::with_capacity(capacity);
    for &value in data {
        output.push((value >> 24) as u8);  // r
        output.push((value >> 16) as u8);  // g
        output.push((value >>  8) as u8);  // b
        output.push((value >>  0) as u8);  // a
    }
    output
}

相关问题