Быстрая сериализация / десериализация структур - PullRequest
11 голосов
/ 30 марта 2012

У меня есть огромное количество географических данных, представленных в простой структуре объекта, состоящей только из структур. Все мои поля имеют тип значения.

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-300 КБ).

Каков наиболее эффективный способ сериализации / десериализации Parent[] в byte[] для создания дампа на диск и чтения обратно? Что касается скорости, Меня особенно интересует быстрая десериализация , скорость записи не так критична.

Достаточно ли просто BinarySerializer хорошо? Или мне стоит взломать StructLayout (см. Принятый ответ) ? Я не уверен, что это будет работать с полем массива Parent.children.

ОБНОВЛЕНИЕ: Ответ на комментарии - Да, объекты неизменны (код обновлен), и действительно, поле children не является типом значения. 300 КБ звучит немного, но у меня есть миллионы таких файлов, поэтому скорость имеет значение.

Ответы [ 2 ]

12 голосов
/ 30 марта 2012

Если вы не желаете идти по маршруту , пишите свой собственный сериализатор , вы можете использовать сериализатор 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;
}

UPDATE:

На самом деле, написать собственный сериализатор довольно просто, вот простая реализация:

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. Он также создает файлы минимального размера, поэтому запись на диск должна выполняться быстрее.

10 голосов
/ 30 марта 2012

BinarySerializer - очень распространенный сериализатор. Он не будет работать так же хорошо, как пользовательская реализация.

К счастью для вас, ваши данные состоят только из структур. Это означает, что вы сможете исправить structlayout для Child и просто скопировать битовый массив детей, используя небезопасный код из байта [], который вы прочитали с диска.

Для родителей это не так просто, потому что вам нужно обращаться с детьми отдельно. Я рекомендую использовать небезопасный код для копирования битовых копируемых полей из байта [], который вы читаете, и десериализации дочерних элементов отдельно.

Рассматривали ли вы отображение всех детей в память, используя файлы, отображенные в память? Затем вы можете повторно использовать средство кэширования операционных систем и вообще не заниматься чтением и записью.

десериализация ребенка с нулевой копией [] выглядит следующим образом:

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];
 }
}
...