使用Serde和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




