You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Serde和Bincode将大型结构体序列化到磁盘速度缓慢

优化大向量Bincode序列化速度的方案

这个问题我之前处理过类似的场景——序列化包含2³¹个u32的超大向量时,Bincode的默认配置确实会因为额外的编码开销拖慢速度,尤其是当数据总量达到8GB级别的时候。下面给你几个从易到难的优化方案,按性能提升幅度排序:

1. 用缓冲IO减少系统调用开销

最基础也最容易实现的优化是给File加上缓冲层。默认的File写入每次调用都会触发系统调用,对于海量数据来说,频繁的系统调用会带来显著的性能损耗。用BufWriter包装后,会先把数据写到内存缓冲区,攒到一定大小再一次性写入磁盘,大幅减少系统调用次数。

修改后的代码示例:

use std::io::BufWriter;

fn main() {
    let m = MyStruct::new();
    // 用BufWriter包装File
    let mut f = BufWriter::new(File::create("mystruct.bin").unwrap());
    
    let options = bincode::options()
        .with_fixint_encoding() // 后面会讲这个配置的作用
        .with_little_endian();
    options.serialize_into(&mut f, &m).unwrap();
}

2. 调整Bincode配置,使用固定长度整数编码

Bincode默认对整数使用可变长度编码(Varint),这种编码对于小整数能节省空间,但对于u32这种固定大小的类型,会额外增加CPU计算开销(要把每个u32转成Varint格式)。改成固定长度编码后,每个u32直接以4字节的原始格式写入,序列化过程几乎没有CPU开销。

关键就是启用with_fixint_encoding()配置,再配合指定字节序(保持读取时一致即可),代码示例如上。这个调整能让序列化速度提升一大截,同时不会破坏Serde的序列化逻辑,可读性和可维护性都很好。

3. 绕过Serde框架,直接写入原始字节

如果上面的优化还达不到你的性能预期,可以彻底跳过Bincode的序列化逻辑,直接把向量的底层字节写入磁盘。因为Vec<u32>的内存是连续的,我们可以直接把它转换成字节切片写入,这是理论上最快的方式——完全没有序列化的CPU开销,纯磁盘IO操作。

不过这种方法需要手动处理结构体的所有字段,读写逻辑要严格对应:

写入代码

use std::io::Write;

fn main() {
    let m = MyStruct::new();
    let mut f = BufWriter::new(File::create("mystruct_raw.bin").unwrap());
    
    // 先写入offset字段:把usize转成固定长度的字节数组(这里用小端序)
    let offset_bytes = m.offset.to_le_bytes();
    f.write_all(&offset_bytes).unwrap();
    
    // 直接写入counter的原始字节:把Vec<u32>转成&[u8]
    let counter_bytes = unsafe {
        std::slice::from_raw_parts(
            m.counter.as_ptr() as *const u8,
            m.counter.len() * std::mem::size_of::<u32>(),
        )
    };
    f.write_all(counter_bytes).unwrap();
}

对应的读取代码(供参考)

use std::io::Read;

fn read_mystruct() -> MyStruct {
    let mut f = BufReader::new(File::open("mystruct_raw.bin").unwrap());
    
    // 读取offset
    let mut offset_bytes = [0u8; std::mem::size_of::<usize>()];
    f.read_exact(&mut offset_bytes).unwrap();
    let offset = usize::from_le_bytes(offset_bytes);
    
    // 读取counter的原始字节并转成Vec<u32>
    let mut counter_vec = Vec::with_capacity(2usize.pow(31));
    let counter_bytes = unsafe {
        std::slice::from_raw_parts_mut(
            counter_vec.as_mut_ptr() as *mut u8,
            counter_vec.capacity() * std::mem::size_of::<u32>(),
        )
    };
    f.read_exact(counter_bytes).unwrap();
    unsafe { counter_vec.set_len(2usize.pow(31)) };
    
    MyStruct { counter: counter_vec, offset }
}

4. 硬件与环境优化

最后别忘了检查磁盘本身的性能:如果用的是机械硬盘(HDD),换成固态硬盘(SSD)能带来数量级的速度提升;另外,确保文件系统没有启用额外的压缩或加密功能(这些都会增加写入时的CPU开销)。


内容的提问来源于stack exchange,提问作者m00am

火山引擎 最新活动