Есть ли у protobuf-net встроенное сжатие для сериализации?

Я проводил некоторое сравнение между BinaryFormatter и сериализатором protobuf-net и был очень доволен тем, что нашел, но странно, что protobuf-net удалось сериализовать объекты в меньший байтовый массив, чем Я бы получил, если бы просто записал значение каждого свойства в массив байтов без метаданных.

Я знаю, что protobuf-net поддерживает интернирование строк, если вы установите для AsReference значение true, но я не делаю этого в этом случае, так обеспечивает ли protobuf-net некоторое сжатие по умолчанию?

Вот код, который вы можете запустить, чтобы увидеть для себя:

var simpleObject = new SimpleObject
                       {
                           Id = 10,
                           Name = "Yan",
                           Address = "Planet Earth",
                           Scores = Enumerable.Range(1, 10).ToList()
                       };

using (var memStream = new MemoryStream())
{
    var binaryWriter = new BinaryWriter(memStream);
    // 4 bytes for int
    binaryWriter.Write(simpleObject.Id);      
    // 3 bytes + 1 more for string termination
    binaryWriter.Write(simpleObject.Name);    
    // 12  bytes + 1 more for string termination
    binaryWriter.Write(simpleObject.Address); 
    // 40 bytes for 10 ints
    simpleObject.Scores.ForEach(binaryWriter.Write); 

    // 61 bytes, which is what I expect
    Console.WriteLine("BinaryWriter wrote [{0}] bytes",
      memStream.ToArray().Count());
}

using (var memStream = new MemoryStream())
{
    ProtoBuf.Serializer.Serialize(memStream, simpleObject);

    // 41 bytes!
    Console.WriteLine("Protobuf serialize wrote [{0}] bytes",
      memStream.ToArray().Count());
}

ОБНОВЛЕНИЕ: забыли добавить, класс SimpleObject выглядит следующим образом:

[Serializable]
[DataContract]
public class SimpleObject
{
    [DataMember(Order = 1)]
    public int Id { get; set; }

    [DataMember(Order = 2)]
    public string Name { get; set; }

    [DataMember(Order = 3)]
    public string Address { get; set; }

    [DataMember(Order = 4)]
    public List<int> Scores { get; set; }
}

Ответы

Ответ 1

Нет, нет; нет "сжатия", как указано в спецификации protobuf; однако он (по умолчанию) использует "varint encoding" - кодировку переменной длины для целочисленных данных, что означает, что малые значения используют меньше места; поэтому 0-127 принимает 1 байт плюс заголовок. Обратите внимание, что varint сам по себе довольно сглажен для отрицательных чисел, поэтому также поддерживается кодировка "зигзагообразная", которая позволяет малым значениям величин быть небольшим (в основном, он чередует положительные и отрицательные пары).

Собственно, в вашем случае для Scores вы также должны посмотреть "упакованную" кодировку, для которой требуется либо [ProtoMember(4, IsPacked = true)], либо эквивалент через TypeModel в v2 (v2 поддерживает любой подход). Это позволяет избежать накладных расходов на заголовок для каждого значения, написав один заголовок и объединенную длину. "Packed" может использоваться с varint/zigzag. Существуют также кодировки фиксированной длины для сценариев, где вы знаете, что значения, вероятно, большие и непредсказуемые.

Обратите также внимание: но если у ваших данных есть много текста, вы можете дополнительно использовать его через gzip или deflate; если это не так, то как gzip, так и deflate могут заставить его стать больше.

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