Быстрая сериализация/десериализация структур
У меня огромный размер географических данных, представленных в простой структуре объектов, состоящей только из структур. Все мои поля имеют тип значения.
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. Каждый массив содержит несколько тысяч родительских экземпляров. У меня слишком много данных, чтобы хранить все в памяти, поэтому мне нужно поменять эти куски на диск взад и вперед. (Один файл будет составлять примерно 2-300 КБ).
Каким будет наиболее эффективный способ сериализации/десериализации Parent[]
в byte[]
для dumpint на диск и чтения? Что касается скорости, Меня особенно интересует быстрая десериализация, скорость записи не является критичной.
Было бы просто BinarySerializer
достаточно хорошо?
Или я должен взломать StructLayout
(см. Принятый ответ)? Я не уверен, что это будет работать с полем массива Parent.children
.
UPDATE: ответ на комментарии. Да, объекты неизменяемы (код обновлен), и действительно, поле children
не является типом значения. 300 КБ звучит не так много, но у меня есть такие файлы, как скорость, поэтому скорость имеет значение.
Ответы
Ответ 1
BinarySerializer - очень общий сериализатор. Он не будет выполняться так же, как пользовательская реализация.
К счастью для ваших, ваши данные состоят только из структур. Это означает, что вы сможете исправить structlayout для Child и просто скопировать дочерний массив с помощью небезопасного кода из байта [], который вы прочитали с диска.
Для родителей это не так просто, потому что вам нужно относиться к детям отдельно. Я рекомендую вам использовать небезопасный код для копирования полей с битами для копирования из байта [], которые вы читаете и десериализируете детей отдельно.
Рассматривалось ли отображение всех детей в память с использованием файлов с отображением памяти? Затем вы можете повторно использовать средство кеширования операционной системы и вообще не заниматься чтением и записью.
Zero-copy-десериализация дочернего элемента [] выглядит следующим образом:
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];
}
}
Ответ 2
Если вы не хотите писать свой собственный маршрут сериализатора, вы можете использовать сериализатор 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-5х).
Чтобы сделать ваши сериализуемые структуры (с 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;
}
UPDATE:
На самом деле, писать пользовательский сериализатор довольно просто, вот реализация bare-bones:
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
Очевидно, что он намного менее гибкий, чем другие подходы, но если скорость действительно важна, то это примерно в 2-3 раза быстрее, чем метод protobuf. Он также создает минимальные размеры файлов, поэтому запись на диск должна быть быстрее.