Кастомная сериализация массива Object[]
От: LWhisper  
Дата: 29.03.16 09:43
Оценка:
Коллеги, подскажите — как бы покрасивее засериализовать массив Object (естественно, third-party классы), чтобы не возникло сложности при их десериализации?
Null, Cross-reference, группировка по типам, дельта-кодирование индексов не рассматривается — всё это есть. Вопрос — как сохранить информацию о типе.
1) Получение уникального идентификатора. Сейчас:
        private Guid GenerateId()
        {
            unsafe
            {
                byte[] buff = new byte[16];
                fixed(byte* numPtr = &buff[0])
                {
                    unchecked
                    {
                        String nameSpace = Type.Namespace;
                        String assemblyQualifiedName = Type.AssemblyQualifiedName;
                        var p1 = Type.GetHashCode();
                        var p2 = nameSpace == null ? 0 : nameSpace.GetHashCode();
                        var p3 = Type.FullName.GetHashCode();
                        var p4 = assemblyQualifiedName == null ? 0 : assemblyQualifiedName.GetHashCode();

                        *(int*)(numPtr + 0) = p1;
                        *(int*)(numPtr + 4) = p1 * 23 ^ p2;
                        *(int*)(numPtr + 8) = p1 * 23 ^ p2 * 47 ^ p3;
                        *(int*)(numPtr + 12) = p1 * 23 ^ p2 * 47 ^ p3 * 191 ^ p4;
                    }
                }
                return new Guid(buff);
            }
        }

Явно наличествуюет дублирующая информация, от которой можно безопасно избавиться. Уникальность такого ID вызывает сомнения, впрочем это не критично — отвалятся автотесты.

2) По такому ID невозможно найти тип, не перебрав все типы во всех загруженных сборках и заново посчитав его для каждого из них. Этого делать не хочется.
Посоветуйте что-нибудь.

Естественно, BinaryFormatter и прочие нативные штуки не рассматриваются. Только самописные велосипеды.
.net reflection сериализация
Re: Кастомная сериализация массива Object[]
От: Sinix  
Дата: 29.03.16 10:21
Оценка: 1 (1) +2
Здравствуйте, LWhisper, Вы писали:

LW>Коллеги, подскажите — как бы покрасивее засериализовать массив Object (естественно, third-party классы), чтобы не возникло сложности при их десериализации?

Вы свою бинарную сериализацию изобретаете, ещё одну к нескольким десяткам существующих?

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

Если хотите подглядеть код из больших проектов — см на orleans или на любой из этих.


LW>Явно наличествуюет дублирующая информация, от которой можно безопасно избавиться. Уникальность такого ID вызывает сомнения, впрочем это не критично — отвалятся автотесты.


Вот так нельзя делать ни при каких условиях. Это явный дисквал для всего фреймворка, поскольку намекает на то, что сначала пишем, затем изучаем матчасть.
Фиг с ним, что у вас гуид не соответствует ни MS guid ни RFC 4122 (хотя для продакшн-кода это сразу до свидания).
У вас _уникальность_ по хэш-кодам строится. Ну нельзя же так

LW>2) По такому ID невозможно найти тип, не перебрав все типы во всех загруженных сборках и заново посчитав его для каждого из них. Этого делать не хочется.

LW>Посоветуйте что-нибудь.
Кэп: список типов и хранение в заголовке объекта номера типа чем не устраивает?
Re[2]: Кастомная сериализация массива Object[]
От: LWhisper  
Дата: 29.03.16 14:31
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Какой из вариантов: для передачи сообщений между сервисами с общей codebase, универсальный протокол, бинарный формат данных?

S>Для каждого из сценариев есть свои нюансы, на которых можно сэкономить.
Универсальный. В данный момент интересует бинарный его вариант.

S>Если хотите подглядеть код из больших проектов — см на orleans или на любой из этих.

Спасибо, подгляжу.

S>Вот так нельзя делать ни при каких условиях. Это явный дисквал для всего фреймворка, поскольку намекает на то, что сначала пишем, затем изучаем матчасть.

S>Фиг с ним, что у вас гуид не соответствует ни MS guid ни RFC 4122 (хотя для продакшн-кода это сразу до свидания).
S>У вас _уникальность_ по хэш-кодам строится. Ну нельзя же так
Поэтому и спрашиваю — как зя? Но автотесты пока спасают.

S>Кэп: список типов и хранение в заголовке объекта номера типа чем не устраивает?

Есть две сборки. В одной 100 типов, в другой 2000. Нужные совпадают по имени и сигнатурам методов. Как получить правильный номер типа, не перечисляя их все вручную?
Re[3]: Кастомная сериализация массива Object[]
От: Sinix  
Дата: 29.03.16 14:36
Оценка: +1
Здравствуйте, LWhisper, Вы писали:

S>>Кэп: список типов и хранение в заголовке объекта номера типа чем не устраивает?

LW>Есть две сборки. В одной 100 типов, в другой 2000. Нужные совпадают по имени и сигнатурам методов. Как получить правильный номер типа, не перечисляя их все вручную?

Как вариант, держать словарик Type=>index. Словарик сериализовать вместе с данными.
Re: Кастомная сериализация массива Object[]
От: Sharov Россия  
Дата: 29.03.16 17:05
Оценка:
Здравствуйте, LWhisper, Вы писали:

К уже сказанному добавить нечего, но есть один вопрос: почему для уникальности типа выбирается самогенерящийся guid, когда можно какой-нибудь хеш-стринг для этой цели использовать? Guid тут совсем не в кассу.
Кодом людям нужно помогать!
Re: Кастомная сериализация массива Object[]
От: VladD2 Российская Империя www.nemerle.org
Дата: 30.03.16 12:53
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Естественно, BinaryFormatter и прочие нативные штуки не рассматриваются. Только самописные велосипеды.


Делаете то что? С какой целью сериализатор ваяете? От целей нужно и реализацию выбирать. Вот эта ваша работа с рефлексией — это точно тормоза.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Кастомная сериализация массива Object[]
От: LWhisper  
Дата: 30.03.16 13:33
Оценка:
S>Как вариант, держать словарик Type=>index. Словарик сериализовать вместе с данными.
Да, думал об этом. Пока наихудший вариант.
Re[2]: Кастомная сериализация массива Object[]
От: LWhisper  
Дата: 30.03.16 13:34
Оценка:
Здравствуйте, VladD2, Вы писали:
VD>Делаете то что? С какой целью сериализатор ваяете? От целей нужно и реализацию выбирать. Вот эта ваша работа с рефлексией — это точно тормоза.

Эта работа с рефлексией работает в 24.8 раза быстрее родного BinaryFormatter. Зачем так категорично?
Сериализовать Object[] с одной стороны, передать по сети, десериализовать.
Re[2]: Кастомная сериализация массива Object[]
От: LWhisper  
Дата: 30.03.16 13:34
Оценка:
S>К уже сказанному добавить нечего, но есть один вопрос: почему для уникальности типа выбирается самогенерящийся guid, когда можно какой-нибудь хеш-стринг для этой цели использовать? Guid тут совсем не в кассу.

Например?
Re[3]: Кастомная сериализация массива Object[]
От: Sharov Россия  
Дата: 30.03.16 13:53
Оценка:
Здравствуйте, LWhisper, Вы писали:

S>>К уже сказанному добавить нечего, но есть один вопрос: почему для уникальности типа выбирается самогенерящийся guid, когда можно какой-нибудь хеш-стринг для этой цели использовать? Guid тут совсем не в кассу.


LW>Например?


md5?
Кодом людям нужно помогать!
Re[3]: Кастомная сериализация массива Object[]
От: VladD2 Российская Империя www.nemerle.org
Дата: 30.03.16 17:10
Оценка: 27 (2) +1
Здравствуйте, LWhisper, Вы писали:

LW>Эта работа с рефлексией работает в 24.8 раза быстрее родного BinaryFormatter. Зачем так категорично?


Напишешь грамотно, будет в 100 раз быстрее работать. Работать быстрее BinaryFormatter очень не сложно, так как там разве что виндовс не переустанваливаеют.

Я много лет назад тоже с этим возился.
http://rsdn.ru/forum/dotnet/362745.1
Автор: Владислав Чистяков
Дата: 24.08.03

http://rsdn.ru/forum/dotnet/244451.1
Автор: VladD2
Дата: 17.04.03

http://rsdn.ru/forum/dotnet/237885.1
Автор: VladD2
Дата: 10.04.03


LW>Сериализовать Object[] с одной стороны, передать по сети, десериализовать.


Это понятно. Тут тем 10 только за последние пару недель проскочило.

Могу поделиться своим опытом. Мы гоняем не по сети, а между процессами по именованным каналам. Используем собственную сериализацию на макросах немерла. Все летает как трофейный Мессершмит! Никакой рефлексии в рнтайме. Все бинарнинько и оптимально. На написание кода сериализатора ушло три дня (в основном его писал человек видящий немерл первый месяц). Вот здесь код макроса сариализации. А вот применение (применение атрибута NitraMessage делает тип сериализуемым):
  [NitraMessage]
  public class Message
  {
    public static AssemblyVersionGuid : string = MakeGuidString();
  }

  [NitraMessage]
  public struct NSpan
  {
    public StartPos : int;
    public EndPos   : int;
    public Length   : int { get { EndPos - StartPos }}
    
    public override ToString() : string { StartPos + ", " + EndPos }
  }
  
  [NitraMessage]
  public struct SpanInfo
  {
    public Span        : NSpan;
    public SpanClassId : int;
  }
  
  [NitraMessage]
  public variant FileChange
  {
    | Insert  { pos: int; text: string }
    | Delete  { span: NSpan }
    | Replace { span: NSpan; text: string }
  }

  [NitraMessage]
  public struct DynamicExtensionInfo
  {
    public Name : string;
    public Path : string;
    
    public override ToString() : string { "DynamicExtension: " + Name }
  }
  
  [NitraMessage]
  public struct LanguageInfo
  {
    public Name              : string;
    public Path              : string;
    public FileExtensions    : ImmutableArray[string];
    public DynamicExtensions : ImmutableArray[DynamicExtensionInfo];
    
    public override ToString() : string { "Language: " + Name }
  }


Ниже приведен декомпилят для последнего типа (LanguageInfo):

using Nemerle.Core;
using Nemerle.Internal;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;

namespace Nitra.ClientServer.Messages
{
    public struct LanguageInfo
    {
        public readonly string Name;

        public readonly string Path;

        public readonly ImmutableArray<string> FileExtensions;

        public readonly ImmutableArray<DynamicExtensionInfo> DynamicExtensions;

        public short MsgId
        {
            get
            {
                return 51;
            }
        }

        public static LanguageInfo Deserialize(BinaryReader reader)
        {
            string name = reader.ReadString();
            string path = reader.ReadString();
            uint len = reader.ReadUInt32();
            if (len < 0u)
            {
                throw new AssertionException("C:\\Users\\vc\\AppData\\Local\\Temp\\_N_GeneratedSource_Nitra.ClientServer.Messages.n", 285, "len >= 0", "");
            }
            checked
            {
                ImmutableArray<string> arg_AE_0;
                if (len == 0u)
                {
                    arg_AE_0 = ImmutableArray.Create<string>();
                }
                else
                {
                    ImmutableArray<string>.Builder builder = ImmutableArray.CreateBuilder<string>();
                    for (uint num = len; num > 0u; num -= 1u)
                    {
                        string value = reader.ReadString();
                        builder.Add(value);
                    }
                    arg_AE_0 = builder.ToImmutable();
                }
                ImmutableArray<string> fileExtensions = arg_AE_0;
                uint len2 = reader.ReadUInt32();
                if (len2 >= 0u)
                {
                    ImmutableArray<DynamicExtensionInfo> arg_18B_0;
                    if (len2 == 0u)
                    {
                        arg_18B_0 = ImmutableArray.Create<DynamicExtensionInfo>();
                    }
                    else
                    {
                        ImmutableArray<DynamicExtensionInfo>.Builder builder2 = ImmutableArray.CreateBuilder<DynamicExtensionInfo>();
                        for (uint num2 = len2; num2 > 0u; num2 -= 1u)
                        {
                            short id = reader.ReadInt16();
                            if (id != 50)
                            {
                                string msg = "Assertion: id == 50S\nAt: C:\\Users\\vc\\AppData\\Local\\Temp\\_N_GeneratedSource_Nitra.ClientServer.Messages.n failed.";
                                Debug.Fail(msg, "");
                            }
                            DynamicExtensionInfo value2 = DynamicExtensionInfo.Deserialize(reader);
                            builder2.Add(value2);
                        }
                        arg_18B_0 = builder2.ToImmutable();
                    }
                    ImmutableArray<DynamicExtensionInfo> dynamicExtensions = arg_18B_0;
                    return new LanguageInfo(name, path, fileExtensions, dynamicExtensions);
                }
                throw new AssertionException("C:\\Users\\vc\\AppData\\Local\\Temp\\_N_GeneratedSource_Nitra.ClientServer.Messages.n", 304, "len >= 0", "");
            }
        }

        public void Serialize(BinaryWriter writer)
        {
            writer.Write(this.MsgId);
            string value = (!(this.Name != null)) ? "" : this.Name;
            writer.Write(value);
            string value2 = (!(this.Path != null)) ? "" : this.Path;
            writer.Write(value2);
            ImmutableArray<string> immutableArray = this.FileExtensions;
            checked
            {
                if (immutableArray.IsDefault)
                {
                    writer.Write((uint)0);
                }
                else
                {
                    writer.Write(immutableArray.Length);
                    ImmutableArray<string>.Enumerator enumerator = immutableArray.GetEnumerator();
                    try
                    {
                        while (enumerator.MoveNext())
                        {
                            string current = enumerator.Current;
                            string elem = current;
                            string value3 = (!(elem != null)) ? "" : elem;
                            writer.Write(value3);
                        }
                    }
                    finally
                    {
                        if (enumerator is IDisposable)
                        {
                            IDisposable disposable = enumerator;
                            disposable.Dispose();
                        }
                    }
                }
                ImmutableArray<DynamicExtensionInfo> immutableArray2 = this.DynamicExtensions;
                if (immutableArray2.IsDefault)
                {
                    writer.Write((uint)0);
                }
                else
                {
                    writer.Write(immutableArray2.Length);
                    ImmutableArray<DynamicExtensionInfo>.Enumerator enumerator2 = immutableArray2.GetEnumerator();
                    try
                    {
                        while (enumerator2.MoveNext())
                        {
                            DynamicExtensionInfo current2 = enumerator2.Current;
                            DynamicExtensionInfo elem2 = current2;
                            elem2.Serialize(writer);
                        }
                    }
                    finally
                    {
                        if (enumerator2 is IDisposable)
                        {
                            IDisposable disposable2 = enumerator2;
                            disposable2.Dispose();
                        }
                    }
                }
            }
        }

        public override string ToString()
        {
            return "Language: " + this.Name;
        }

        [RecordCtor]
        public LanguageInfo([MappedMember("Name")] string name, [MappedMember("Path")] string path, [MappedMember("FileExtensions")] ImmutableArray<string> fileExtensions, [MappedMember("DynamicExtensions")] ImmutableArray<DynamicExtensionInfo> dynamicExtensions)
        {
            this.Name = name;
            this.Path = path;
            this.FileExtensions = fileExtensions;
            this.DynamicExtensions = dynamicExtensions;
        }
    }
}
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Отредактировано 30.03.2016 22:11 VladD2 . Предыдущая версия .
Re[4]: Кастомная сериализация массива Object[]
От: Sinix  
Дата: 30.03.16 19:47
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Все летает как трофейный Мессершмит! Никакой рефлексии в рнтайме.


А оно работает с произвольным набором типов (не известных в момент компиляции)?
Если да — можешь рассказать, как?
Re[5]: Кастомная сериализация массива Object[]
От: VladD2 Российская Империя www.nemerle.org
Дата: 30.03.16 22:30
Оценка: 44 (1)
Здравствуйте, Sinix, Вы писали:

S>А оно работает с произвольным набором типов (не известных в момент компиляции)?

S>Если да — можешь рассказать, как?

Нет. Данная макра рассчитана на сериализацию сообщений которые ходят между нашим клентом и сервером.

Исходно было решено, что нам не нужна поддержка внешних типов короме некоторого набора коллекций. Так же было решено, что нам не нужна версионность. Мы просто проверяем (банальной передачей сообщения с гуидом генерящимся другим макросом при компиляции), что на клиенте и сервере используется одна и та же интерфейсная библиотека.

Специализация позволяет сделать код проще и эффективнее.

У нас есть и более сложная бинарная сериализация используемая для сериализации и десериализации метаданных в нитровских языках.

Там все сложнее. Поддерживается внешняя расширяемость (для этого нужно реализовать специальный интерфейс ISerializable) и поддерживаются рекурсивные объекты (правда специализированные — наши символы). Это опять же специализированная сериализация. Ее писали в расчете на возможность загрузки типов из потока сформированного предыдущей их версией (при появлении или удалении в типах полей) и сериализации сложных графов объектов. Скорость и там не была последним делом, но при сериализации сообщений летающих между процессами этому пришлось уделить больше времени.

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

Можно конечно заняться всемогутором, но это дело неблагодарное. Придется лепить тучу настроек и тучу точек расширения.

Ну, а медленную версию можно и на рефлексии очень не сложно наваять.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Отредактировано 31.03.2016 8:16 VladD2 . Предыдущая версия . Еще …
Отредактировано 31.03.2016 7:58 VladD2 . Предыдущая версия .
Re[5]: Кастомная сериализация массива Object[]
От: VladD2 Российская Империя www.nemerle.org
Дата: 30.03.16 22:36
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Если да — можешь рассказать, как?


Собственно проблема поддержки произвольного типа заключается только в том, что нужно знать как с него читать данные и как их в него писать.

Не трудно создать тип данные которого будет вообще невозможно считать извне без применения рефлексии или хардкороного ансэйф-кода.

Если, скажем, речь идет о типах у которых все доступные данные доступны через публичные поля, а инициализируются они через конструктор, то написать макрос который будет генерировать код сериализации / десериализации не сложно.

Тоже касается и для классов с изменяемыми публичными полями/свойствами.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Кастомная сериализация массива Object[]
От: Sinix  
Дата: 31.03.16 05:27
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Вообще написать одну сериализацию которая подойдет для всех случаев жизни, наверно, невозможно. Макры позволяют делать нужные специализированные версии.

Согласен целиком и полностью. Сколько не сталкивался, вечно получается "быстро, надёжно, универсально, выберите что-то одно".
Re[6]: Кастомная сериализация массива Object[]
От: Sinix  
Дата: 31.03.16 05:31
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Если, скажем, речь идет о типах у которых все доступные данные доступны через публичные поля, а инициализируются они через конструктор, то написать макрос который будет генерировать код сериализации / десериализации не сложно.


Не, это понятно, вопрос был в том, как то же самое сделать для сторонних и заранее неизвестных типов.
Только кодогенерацией в рантайме, что дело очень неблагодарное
Re[4]: Кастомная сериализация массива Object[]
От: LWhisper  
Дата: 31.03.16 06:47
Оценка:
S>md5?
md5 отчего? И что делать в случае коллизии хэшей?
Re[4]: Кастомная сериализация массива Object[]
От: Albeoris  
Дата: 31.03.16 07:10
Оценка:
VD>Могу поделиться своим опытом. Мы гоняем не по сети, а между процессами по именованным каналам. Используем собственную сериализацию на макросах немерла. Все летает как трофейный Мессершмит! Никакой рефлексии в рнтайме. Все бинарнинько и оптимально. На написание кода сериализатора ушло три дня (в основном его писал человек видящий немерл первый месяц). Вот здесь код макроса сариализации. А вот применение (применение атрибута NitraMessage делает тип сериализуемым).
Как мне это представляется, основная проблема как раз с полиморфизмом и сторонними типами.
"Хаос всегда побеждает порядок, поскольку лучше организован." (с) Терри Пратчетт
Отредактировано 31.03.2016 7:13 Albeoris . Предыдущая версия . Еще …
Отредактировано 31.03.2016 7:11 Albeoris . Предыдущая версия .
Re[4]: Кастомная сериализация массива Object[]
От: LWhisper  
Дата: 31.03.16 07:24
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Напишешь грамотно, будет в 100 раз быстрее работать. Работать быстрее BinaryFormatter очень не сложно, так как там разве что виндовс не переустанваливаеют.

Буду стараться!

VD>Я много лет назад тоже с этим возился.

VD>http://rsdn.ru/forum/dotnet/362745.1
Автор: Владислав Чистяков
Дата: 24.08.03

VD>http://rsdn.ru/forum/dotnet/244451.1
Автор: VladD2
Дата: 17.04.03

VD>http://rsdn.ru/forum/dotnet/237885.1
Автор: VladD2
Дата: 10.04.03

К сожалению, это уже всё реализовано (включая хитрые сценарии вроде каста DBNull и примитивных типов в NullableEnum).

VD>Могу поделиться своим опытом. Мы гоняем не по сети, а между процессами по именованным каналам. Используем собственную сериализацию на макросах немерла. Все летает как трофейный Мессершмит! Никакой рефлексии в рнтайме. Все бинарнинько и оптимально. На написание кода сериализатора ушло три дня (в основном его писал человек видящий немерл первый месяц). Вот здесь код макроса сариализации. А вот применение (применение атрибута NitraMessage делает тип сериализуемым).

Примерно так сделано и у меня. И быстрый, простой вариант, зная с какими типами он работает, прекрасно справляется со своей задачей (даже с поддержкой версионности). Но вот считать из потока байт объект, зная лишь что он наследуется от Object — вот это задача.

Сейчас уж родилась совсем бредовая мысль — вместе с сериализованными данными передавать количество известных сторонних типов, которые были сериализованы. И если на таргете оно меньше, запрашивать с сорса недостающие. Для сетевого и межпроцессорного взаимодействия сгодится. А вот для постоянного хранения придётся как-то информацию о типах сохранять. Либо FullName для быстрого поиска, либо какой-то хэш для уменьшения объема.
Re[5]: Кастомная сериализация массива Object[]
От: VladD2 Российская Империя www.nemerle.org
Дата: 31.03.16 09:25
Оценка:
Здравствуйте, LWhisper, Вы писали:

LW>Но вот считать из потока байт объект, зная лишь что он наследуется от Object — вот это задача.


Мы читаем из потока все что угодно. Просто пишем вначале уникальный код типа, а потом вызываем функцию знающую по какому коду что создать.

В нашем случае все несколько проще, так как мы намеренно ограничили список типов типами из одной сборки. Все Id-шники генерируются макрой на стадии компиляции. Она же генерирует статическую функцию знающую по какому айдишнику что создавать. Цена вопроса один short на объект. При этом поддерживается наследование.

LW>Сейчас уж родилась совсем бредовая мысль — вместе с сериализованными данными передавать количество известных сторонних типов, которые были сериализованы. И если на таргете оно меньше, запрашивать с сорса недостающие. Для сетевого и межпроцессорного взаимодействия сгодится. А вот для постоянного хранения придётся как-то информацию о типах сохранять. Либо FullName для быстрого поиска, либо какой-то хэш для уменьшения объема.


У нас все типы которые могу ездить по сети находятся в одной сборке. Мы просто проверяем, что на обоих концах одна и та же ее весрия. Для этого в первом сообщении посылается гуид хранящийся в статическом поле. На сервере он проверяется с этим же полем. Значение гуида меняется при перекомпиляции. Так что мы всегда знаем, что на клинте и сервере одна версия сборки. А раз так, то все приседания излишни.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.