tgoop.com/logofalprog/53
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