结构的快速序列化/反序列化
本文关键字:反序列化 序列化 结构 | 更新日期: 2023-09-27 18:29:28
我有大量的地理数据,这些数据以仅由结构组成的简单对象结构表示。我的所有字段都是值类型。
public struct Child
{
readonly float X;
readonly float Y;
readonly int myField;
}
public struct Parent
{
readonly int id;
readonly int field1;
readonly int field2;
readonly Child[] children;
}
数据被很好地分块为Parent[]
-s的一小部分。每个数组包含几千个Parent实例。我有太多的数据无法全部保存在内存中,所以我需要将这些块来回交换到磁盘上。(一个文件的大小约为2-300KB)。
将Parent[]
序列化/反序列化为byte[]
以转储到磁盘并读回的最有效方法是什么?关于速度,我对快速反序列化特别感兴趣,写速度并不是那么关键。
简单的BinarySerializer
足够好吗?或者我应该用StructLayout
破解一下(见公认答案)?我不确定这是否适用于Parent.children
的数组字段。
UPDATE:对注释的响应-是的,对象是不可变的(代码更新),实际上children
字段不是值类型。300KB听起来不多,但我有无数这样的文件,所以速度很重要。
如果您不想走编写自己的序列化程序的路线,可以使用protobuf.net序列化程序。以下是一个小型测试程序的输出:
Using 3000 parents, each with 5 children BinaryFormatter Serialized in: 00:00:00.1250000 Memory stream 486218 B BinaryFormatter Deserialized in: 00:00:00.1718750 ProfoBuf Serialized in: 00:00:00.1406250 Memory stream 318247 B ProfoBuf Deserialized in: 00:00:00.0312500
这应该是不言自明的。这只是一次跑步,但很好地表明了我看到的速度(3-5x)。
要使结构可序列化(使用protobuf.net),只需添加以下属性:
[ProtoContract]
[Serializable]
public struct Child
{
[ProtoMember(1)] public float X;
[ProtoMember(2)] public float Y;
[ProtoMember(3)] public int myField;
}
[ProtoContract]
[Serializable]
public struct Parent
{
[ProtoMember(1)] public int id;
[ProtoMember(2)] public int field1;
[ProtoMember(3)] public int field2;
[ProtoMember(4)] public Child[] children;
}
更新:
实际上,编写自定义序列化程序非常容易,这里有一个基本实现:
class CustSerializer
{
public void Serialize(Stream stream, Parent[] parents, int childCount)
{
BinaryWriter sw = new BinaryWriter(stream);
foreach (var parent in parents)
{
sw.Write(parent.id);
sw.Write(parent.field1);
sw.Write(parent.field2);
foreach (var child in parent.children)
{
sw.Write(child.myField);
sw.Write(child.X);
sw.Write(child.Y);
}
}
}
public Parent[] Deserialize(Stream stream, int parentCount, int childCount)
{
BinaryReader br = new BinaryReader(stream);
Parent[] parents = new Parent[parentCount];
for (int i = 0; i < parentCount; i++)
{
var parent = new Parent();
parent.id = br.ReadInt32();
parent.field1 = br.ReadInt32();
parent.field2 = br.ReadInt32();
parent.children = new Child[childCount];
for (int j = 0; j < childCount; j++)
{
var child = new Child();
child.myField = br.ReadInt32();
child.X = br.ReadSingle();
child.Y = br.ReadSingle();
parent.children[j] = child;
}
parents[i] = parent;
}
return parents;
}
}
以下是在简单的速度测试中运行时的输出:
Custom Serialized in: 00:00:00 Memory stream 216000 B Custom Deserialized in: 00:00:00.0156250
显然,它的灵活性远不如其他方法,但如果速度真的那么重要,它大约比protobuf方法快2-3倍。它产生的文件大小也很小,所以写入磁盘的速度应该更快。
BinarySerializer是一个非常通用的序列化程序。它的性能不如自定义实现。
幸运的是,您的数据仅由结构组成。这意味着您将能够修复Child的structlayout,并使用从磁盘读取的字节[]中的不安全代码对Child数组进行位复制。
对于父母来说,这并不容易,因为你需要分开对待孩子。我建议您使用不安全的代码从您读取的byte[]中复制位可复制字段,并分别对子级进行反序列化。
你是否考虑过使用内存映射文件将所有孩子映射到内存中?然后,您可以重用操作系统缓存设施,而根本不处理读写操作。
零副本反序列化子[]如下所示:
byte[] bytes = GetFromDisk();
fixed (byte* bytePtr = bytes) {
Child* childPtr = (Child*)bytePtr;
//now treat the childPtr as an array:
var x123 = childPtr[123].X;
//if we need a real array that can be passed around, we need to copy:
var childArray = new Child[GetLengthOfDeserializedData()];
for (i = [0..length]) {
childArray[i] = childPtr[i];
}
}