2009-02-10 3 views
6

Мы начинаем развертывать все больше развертываний WAN нашего продукта (.NET-клиент с хостингом IIS Remoting backend). Из-за этого мы пытаемся уменьшить размер данных на проводе.Оптимальная сериализация примитивных типов

Мы переопределили сериализацию по умолчанию, реализовав ISerializable (аналогично this), и мы видим от 12% до 50% прибыли. Большинство наших усилий сосредоточено на оптимизации массивов примитивных типов. Есть ли причудливый способ сериализации примитивных типов, помимо очевидных?

К примеру, сегодня мы сериализовать массив целых чисел следующим образом:

[4 байта (длина массива)] [4-байт] [4-байт]

Может кто-то делает значительно лучше?

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

Примечание: Сохранение 7 бит на bool может показаться пустой тратой времени, но когда вы имеете дело с большими величинами данных (что мы и есть), оно складывается очень быстро.

Примечание: Мы хотим избежать общих алгоритмов сжатия из-за связанной с ним задержки. Remoting поддерживает только буферизованные запросы/ответы (без кодировки). Я понимаю, что между сжатием и оптимальной сериализацией существует тонкая грань, но наши тесты показывают, что мы можем позволить себе очень специфическую оптимизацию сериализации при очень низкой стоимости задержек. В то время как переработка всего буферизованного ответа в новый сжатый буфер слишком дорога.

+0

(обратите внимание, что я ответил на ваш комментарий/вопрос) –

ответ

5

(относится к сообщениям/классы, а не только примитивы)

Google спроектированные «буферы протокола» для этого типа сценария (они смещаются огромное количество данных вокруг) - их формат компактен (используя такие вещи, как основы -128), но расширяемость и допустимость версий (поэтому клиенты и серверы могут легко обновляться).

В мире .NET, я могу порекомендовать 2 протокола буферов реализации:

Для информации, Protobuf-нетто имеет прямую поддержку для ISerializable и удаляет (это часть unit tests). Показатели производительности/размера here.

И, самое главное, все, что вы делаете, это добавить несколько атрибутов к вашим классам.

Предостережение: оно не претендует на то, чтобы быть теоретическим лучшим - но прагматичным и простым в праве - это компромисс между производительностью, мобильностью и простотой.

+0

Марк - я загрузил последний источник для protobuf-net и запустил пример. Если я не ошибаюсь, похоже, что protobuf-net и BinarySerializer производят примерно такой же размер: DatabaseCompatRem || protobuf-net || 133,010 || 8,614 || 18,853 || || BinaryFormatter || 133,167 || 10,171 || 19,526 || –

+0

Ожидается ли это, или я делаю что-то неправильно или даже смотрю на неправильный пример? –

+0

Ах, нет: это не то, что означает этот результат ... это сравнение * raw * protobuf-net (Serializer.Blah) и BinaryFormatter, который ** использует ** protobuf-net через ISerializer.Вам нужно сравнить результат BinaryFormatter без реализации ISerializable. –

2

Проверьте тип base-128 varint, используемый в буферах протокола Google; это может быть то, что вы ищете.

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

+0

2 из более крупных были реализованы частыми пользователями stackoverflow. –

+0

protobuf-net by me, dotnet-protobufs от Jon Skeet –

0

Если вы знаете, какие значения Int более распространены, вы можете кодировать эти значения в меньшем количестве бит (и кодировать менее общие значения, используя соответственно большее количество бит): это называется кодировкой/кодированием/сжатием «Хаффман».

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

+0

Мы хотим держаться подальше от сжатия по нескольким причинам. В основном это латентность, которую она вводит. Remoting поддерживает только буферизованный ответ/запрос (без кодировки). –

0

Для целого числа, если у вас обычно есть небольшие числа (до 127 или 32768), вы можете кодировать число, используя MSB в качестве флага, чтобы определить, является ли это последним байтом или нет. Немного похож на UTF-8, но бит флага фактически впустую (который не в случае с UTF-8)

Пример (большой обратный порядок байт):

125 which is usually encoded as 00 00 00 7D 
Could be encoded as 7D 

270 which is usually encoded as 00 00 01 0E 
Could be encoded as 82 0E 

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

Этот метод фактически используется в старых форматах, таких как MIDI, потому что для старой электроники нужны очень эффективные и простые методы кодирования.

-1

Перед тем, как внедрить ISerializable, вы, вероятно, использовали XmlSerializer или форматер SOAP в веб-службе. Учитывая, что у вас есть все толстые клиенты, все из которых работают .NET, вы можете попробовать использовать BinaryFormatter.

+0

XmlSerializer был первым делом :-) –

+0

BinaryFormatter должен быть примерно таким же компактным, каким вы хотели бы получить. –

+1

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

1

Если ваши массивы можно отсортировать, вы можете выполнить простое RLE, чтобы сэкономить место. Даже если они не отсортированы, RLE может быть полезен. Он быстро реализуется как для записи, так и для чтения.

+0

Хорошая идея - однако мы не можем сортировать данные, и маловероятно, что несортированные данные будут содержать последовательные дубликаты. –

1

Вот трюк я использовал один раз для кодирования массивов чисел:

  1. Группа элементов массива в группах 4.
  2. перед каждой группы с байтом (назовем его длина маски), что указывает длину из следующих 4 элементов. Маска длины - это битовая маска, состоящая из дибитов, которые указывают, как долго соответствует соответствующий элемент (00 - 1 байт, 01 - 2 байта, 10 - 3 байта, 11 - 4 байта).
  3. Выпишите элементы как можно короче.

Например, для представления целых чисел без знака 0x0000017B, 0x000000A9, 0xC247E8AD и 0x00032A64, можно было бы написать (предполагая, что мало-Endian): B1, 7b, 01, А9, AD, Е8, 47, С2, 64, 2A , 03.

Это может сэкономить до 68,75% (11/16) места в лучшем случае. В худшем случае вы фактически будете тратить дополнительные 6,25% (1/16).

+0

Любая идея, как это будет сравниваться с базой-128 varint Greg? –

+0

Кодировка varint - это та же кодировка, что и в формате OASIS SEMI. Я помню, что мы сравнили два метода (назад на работу, где я работал с этим материалом), и было заключено, что сжатие было немного лучше. Однако реальный выигрыш был быстрым: наш метод лучше работал на C;) –

2

Да, есть причудливый способ сериализации примитивных типов. В качестве бонуса он также намного быстрее (обычно 20-40 раз).

Библиотека с открытым исходным кодом Саймона Хьюитта, см. Optimizing Serialization in .NET - part 2, использует различные трюки. Например, если известно, что массив содержит малые целые числа, тогда меньшее число выводится на сериализованный вывод. Это подробно описано in part 1 of the article. Например:

...Так, Int32, что меньше, чем 128 могут быть сохранены в одном байте (с использованием 7-битного кодирования ) ....

Полное и размер оптимизированы целые числа, что могут быть смешаны и подобраны. Это может показаться очевидным, но есть и другие вещи; например, происходят особые вещи для целочисленного значения 0 - оптимизация для хранения числового типа и нулевого значения.

Часть 1 гласит:

... Если вы когда-либо использовали .NET Remoting для больших объемов данных, вы обнаружили, что есть проблемы с масштабируемостью. Для небольших объемов данных он работает достаточно хорошо, но большие объемы занимают много центрального процессора и памяти, генерируют огромное количество данных для передачи и могут выходить из строя с исключениями Out Of Memory. Существует также большая проблема с временем, затраченным на самом деле выполнить сериализацию - большие объемы данных могут сделать невозможный для использования в приложениях ....

Я использовал эту библиотеку с большим успехом в my application.

Чтобы убедиться, что .NET сериализации никогда не используется Погружает ASSERT 0, Debug.WriteLine() или подобное в место в коде библиотеки, где она падает обратно на .NET сериализации. Это в конце функции WriteObject() в файле FastSerializer.cs, рядом с createBinaryFormatter().Serialize(BaseStream, value);.

0

Если вы хотите самостоятельно управлять форматом сериализации, используя только библиотечную помощь для компактного целочисленного хранения, выведите класс из BinaryWriter, который использует Write7BitEncodedInt. Аналогично для BinaryReader.Read7BitEncodedInt.

Смежные вопросы