就地转换 Vec<u32> 为 Vec<u8>,开销最小

问题描述 投票:0回答:5

我正在尝试将

Vec
u32
s 转换为
Vec
u8
s,最好是就地且没有太多开销。

我当前的解决方案依赖于不安全的代码来重新构建

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 游乐场链接

casting rust unsafe
5个回答
19
投票
  1. 每当写一个

    unsafe
    块时,我强烈鼓励人们在块上添加评论解释为什么你认为代码实际上是安全的。这类信息对以后阅读代码的人很有用

  2. 不要添加关于“幻数”4 的评论,只需使用

    mem::size_of::<u32>
    。我什至会使用
    size_of
    来代表
    u8
    并执行除法以获得最大的清晰度。

  3. 您可以从

    unsafe
    块返回新创建的Vec。

  4. 如评论中所述,像这样“转储”数据块使数据格式平台依赖;你会在小端和大端系统上得到不同的答案。这可能会导致将来出现大量调试问题。文件格式要么将平台字节序编码到文件中(使读者的工作更难),要么只将特定的字节序写入文件(使作者的工作更难)。

  5. 我可能会将整个

    unsafe
    块移动到一个函数并给它起一个名字,只是为了组织目的。

  6. 不需要导入

    Vec
    ,在前奏中

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

游乐场

我对这段代码最大的未知担忧在于与

Vec
相关的内存对齐。

Rust 的底层分配器 allocates and deallocates memory with a specific

Layout
Layout
包含指针的sizealignment等信息。

我假设此代码需要

Layout
来匹配对
alloc
dealloc
的成对调用。如果是这种情况,删除由
Vec<u8>
构造的
Vec<u32>
可能会告诉分配器错误的对齐方式
,因为该信息是 基于元素类型

如果没有更好的知识,“最好”的做法就是让

Vec<u32>
保持原样并简单地获得
&[u8]
。切片与分配器没有交互,避免了这个问题。

即使不与分配器交互,您也需要注意对齐!

另见:


6
投票

如果就地转换不是强制性的,像这样管理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);
}

1
投票

要正确地进行这种转换,您需要通过 Vec 的关联分配器并调用 shrink 将布局转换为新的对齐方式,然后再调用

from_raw_parts
。这取决于分配器能够执行就地重新分配。

如果您不需要调整结果向量的大小,那么将 vec 的

&mut [u32]
借用重新解释为
&mut [u8]
将是一个更简单的选择。


0
投票

这就是我使用移位副本解决问题的方法。

它可以在我的 x64 机器上运行,但我不确定我是否对小/大字节序做出了不安全的假设。

如果可以在不需要副本的情况下就地完成此转换,运行时性能会更快,但我还没有想出如何做到这一点。

/// 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
}

0
投票

试试这个

let vec32: Vec<u32> = vec![1u32, 2u32];
let mut vec8: Vec<u8> = vec![];
for v in &vec32{
    for b in v.to_be_bytes(){
        vec8.push(b);
    }
}
println!("{:?}", vec32);
println!("{:?}", vec8);

游乐场

© www.soinside.com 2019 - 2024. All rights reserved.