LOGOFALPROG Telegram 53
Сериализация. Часть 2: фатальный недостаток MessagePack
#кодище
Продолжим рассказ про сериализацию. Перебрав кучу вариантов, я наконец обратил внимание на проекты чувака по имени Yoshifumi Kawai, более известного как neuecc. Этот японец написал целый выводок сериализаторов под разные задачи для .Net: UTF8Json, ZeroFormatter, MessagePack.

UTF8Json предоставляет человекочитаемый, но медленный формат; ZeroFormatter заточен на скорость, но создаёт большие файлы; а MessagePack являет собой эдакий Protobuf на максималках. У него достаточно высокая скорость, но при этом крошечный бинарный формат и поддерживается версионность. Более того, поскольку он сразу писался именно для .Net, а не универсально, он лишён недостатка Protobuf, чья система типов не может отличить null от пустой коллекции.

Внутренности MessagePack тоже выше всяких похвал и оптимизированы просто донельзя. Функции сериализации классов там генерируются на старте прямо из op-кодов IL, а API использования не вызывает никаких аллокаций. Более того, инты с мелкими значениями пишут в файл меньше 4 байт (7 бит каждого байта отводится на число, а последний бит указывает, влезло ли число или нужно прочитать следующий байт). В общем, мечта.

Но если с низким уровнем всё хорошо, то наверху обнаружились фатальные недостатки. Чтобы понять, в чём проблема, нужно объяснить тамошний формат. MessagePack сериализует объекты в некоторое подобие BSON (Binary JSON). То есть он записывает последовательность бинарных токенов, таких как «начало массива из N элементов», «начало ассоциативного массива», «число», «строка» и т.д. Если прочитать это, то можно получить строку в формате очень похожем на JSON (с небольшими отличиями).

MessagePack также имеет два варианта атрибуции. Вариант с текстовыми ключами:

class A
{
[Key("foo")] string Foo = "Text";
[Key("bar")] int Bar = 777;
}


создаст следующую JSON-строку {"foo":"Text","bar":777}, что, как вы понимаете, не очень хорошо, так как названия ключей будут писаться в файл сотни раз. Поэтому есть аттрибуция интами:

class A
{
[Key(0)] string Foo = "Text";
[Key(3)] int Bar = 777;
}


что породит простой массив [“Text”, null, null, 777], где номер поля обозначает его положение внутри массива (что может приводить к дырам).

Также есть возможность атрибутами перечислить у абстрактного класса все виды его конкретных типов. И тогда при записи сначала будет номер конкретного типа, а уже потом во вложенном массиве содержимое его полей:

[5,[“Text”, null, null, 777]]


Так что же не так с форматом? Во-первых, в такой схеме полиморфизм наследования работает только для абстрактных классов. Если же пытаться сохранить массив конкретных типов (но от которых можно наследоваться), то информация о реальных типах потеряется. Во-вторых, MessagePack предполагает, что ключи атрибуции полей уникальны не только для самого класса, но и для всех его базовых классов. А это крайне тяжело гарантировать, если дерево наследования будет разрастаться, либо же придётся делать большие зазоры между номерами, что приведёт к сильному увеличению бинаря.

Благо, библиотека имеет MIT-лицензию и отлично расширяется. Поэтому я написал полностью свою атрибуцию, разбор её рефлекшеном и кастомные резолверы и форматтеры для записи в файл. Каждый конкретный класс у меня получает через атрибут свой глобальный номер (новое значение в большом enum’e), а атрибуция полей уникальна только в рамках данного подкласса. Таким образом, в файл пишется примерно следующее:

[5,[null, 17, 43],[88, false],[“Text”, null, null, 777]]


Где, 5 это номер класса, а каждый «вагончик» — это поля каждого класса от базового до конкретного. Причём номер пишется только в тех ситуациях, когда действительно необходим, а вагончики записываются только для классов, которые реально имеют данные.

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

To be continued… Обсудить



tgoop.com/logofalprog/53
Create:
Last Update:

Сериализация. Часть 2: фатальный недостаток MessagePack
#кодище
Продолжим рассказ про сериализацию. Перебрав кучу вариантов, я наконец обратил внимание на проекты чувака по имени Yoshifumi Kawai, более известного как neuecc. Этот японец написал целый выводок сериализаторов под разные задачи для .Net: UTF8Json, ZeroFormatter, MessagePack.

UTF8Json предоставляет человекочитаемый, но медленный формат; ZeroFormatter заточен на скорость, но создаёт большие файлы; а MessagePack являет собой эдакий Protobuf на максималках. У него достаточно высокая скорость, но при этом крошечный бинарный формат и поддерживается версионность. Более того, поскольку он сразу писался именно для .Net, а не универсально, он лишён недостатка Protobuf, чья система типов не может отличить null от пустой коллекции.

Внутренности MessagePack тоже выше всяких похвал и оптимизированы просто донельзя. Функции сериализации классов там генерируются на старте прямо из op-кодов IL, а API использования не вызывает никаких аллокаций. Более того, инты с мелкими значениями пишут в файл меньше 4 байт (7 бит каждого байта отводится на число, а последний бит указывает, влезло ли число или нужно прочитать следующий байт). В общем, мечта.

Но если с низким уровнем всё хорошо, то наверху обнаружились фатальные недостатки. Чтобы понять, в чём проблема, нужно объяснить тамошний формат. MessagePack сериализует объекты в некоторое подобие BSON (Binary JSON). То есть он записывает последовательность бинарных токенов, таких как «начало массива из N элементов», «начало ассоциативного массива», «число», «строка» и т.д. Если прочитать это, то можно получить строку в формате очень похожем на JSON (с небольшими отличиями).

MessagePack также имеет два варианта атрибуции. Вариант с текстовыми ключами:

class A
{
[Key("foo")] string Foo = "Text";
[Key("bar")] int Bar = 777;
}


создаст следующую JSON-строку {"foo":"Text","bar":777}, что, как вы понимаете, не очень хорошо, так как названия ключей будут писаться в файл сотни раз. Поэтому есть аттрибуция интами:

class A
{
[Key(0)] string Foo = "Text";
[Key(3)] int Bar = 777;
}


что породит простой массив [“Text”, null, null, 777], где номер поля обозначает его положение внутри массива (что может приводить к дырам).

Также есть возможность атрибутами перечислить у абстрактного класса все виды его конкретных типов. И тогда при записи сначала будет номер конкретного типа, а уже потом во вложенном массиве содержимое его полей:

[5,[“Text”, null, null, 777]]


Так что же не так с форматом? Во-первых, в такой схеме полиморфизм наследования работает только для абстрактных классов. Если же пытаться сохранить массив конкретных типов (но от которых можно наследоваться), то информация о реальных типах потеряется. Во-вторых, MessagePack предполагает, что ключи атрибуции полей уникальны не только для самого класса, но и для всех его базовых классов. А это крайне тяжело гарантировать, если дерево наследования будет разрастаться, либо же придётся делать большие зазоры между номерами, что приведёт к сильному увеличению бинаря.

Благо, библиотека имеет MIT-лицензию и отлично расширяется. Поэтому я написал полностью свою атрибуцию, разбор её рефлекшеном и кастомные резолверы и форматтеры для записи в файл. Каждый конкретный класс у меня получает через атрибут свой глобальный номер (новое значение в большом enum’e), а атрибуция полей уникальна только в рамках данного подкласса. Таким образом, в файл пишется примерно следующее:

[5,[null, 17, 43],[88, false],[“Text”, null, null, 777]]


Где, 5 это номер класса, а каждый «вагончик» — это поля каждого класса от базового до конкретного. Причём номер пишется только в тех ситуациях, когда действительно необходим, а вагончики записываются только для классов, которые реально имеют данные.

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

To be continued… Обсудить

BY Log of Alprog


Share with your friend now:
tgoop.com/logofalprog/53

View MORE
Open in Telegram


Telegram News

Date: |

How to build a private or public channel on Telegram? While the character limit is 255, try to fit into 200 characters. This way, users will be able to take in your text fast and efficiently. Reveal the essence of your channel and provide contact information. For example, you can add a bot name, link to your pricing plans, etc. Image: Telegram. Joined by Telegram's representative in Brazil, Alan Campos, Perekopsky noted the platform was unable to cater to some of the TSE requests due to the company's operational setup. But Perekopsky added that these requests could be studied for future implementation. So far, more than a dozen different members have contributed to the group, posting voice notes of themselves screaming, yelling, groaning, and wailing in various pitches and rhythms.
from us


Telegram Log of Alprog
FROM American