C# 协议缓冲区使用大量非基元类型进行序列化

C# 协议缓冲区使用大量非基元类型进行序列化,c#,serialization,protocol-buffers,protobuf-net,C#,Serialization,Protocol Buffers,Protobuf Net,我正在将我的一些DataContractSerializer使用转换为协议缓冲区序列化(特别是使用protobuf net),目的是更快地序列化并减小用于存储在数据库blob中的序列化数据大小 我发现更改对象模型对消息大小有很大影响。我认为这意味着,由于我选择了对象模型,我的序列化数据被人为地夸大了,我想解决这个问题 具体地说,我的问题是:我是否可以更改protobuf网络使用,或者可能更改序列化库,以获得更小的消息大小?下面我将给出一个对象模型,以及迄今为止我所能理解的内容 在我的例子中,我正

我正在将我的一些DataContractSerializer使用转换为协议缓冲区序列化(特别是使用protobuf net),目的是更快地序列化并减小用于存储在数据库blob中的序列化数据大小

我发现更改对象模型对消息大小有很大影响。我认为这意味着,由于我选择了对象模型,我的序列化数据被人为地夸大了,我想解决这个问题

具体地说,我的问题是:我是否可以更改protobuf网络使用,或者可能更改序列化库,以获得更小的消息大小?下面我将给出一个对象模型,以及迄今为止我所能理解的内容

在我的例子中,我正在序列化OCR数据。。。以下是一个简化的对象模型:

[ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)]
public class OcrTable
{
    [ProtoMember(1)]        
    public List<OcrTableCell> Cells;
}

[ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)]
public class OcrTableCell
{
    [ProtoMember(1)]
    public int Row;
    [ProtoMember(2)]
    public int Column;
    [ProtoMember(3)]
    public int RowSpan;

    //...

    [ProtoMember(10)]
    public int Height;

    [ProtoMember(11)]
    public List<OcrCharacter> Characters;
}

[ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)]
public class OcrCharacter
{
    [ProtoMember(1)]
    public int Code;
    [ProtoMember(2)]
    public int Data;
    [ProtoMember(3)]
    public int Confidence;

    //...

    [ProtoMember(11)]
    public int Width;
}
在这里,您可以看到我用多个列表替换了
List
:在
OcrCharacter
中,每个字段一个列表。这对序列化数据大小有相当大的影响,在某些情况下会减少三分之二(即使在gzip之后)

我认为仅仅为了支持序列化而对我的对象模型进行这样的更改是不现实的。。。保留第二个“助手”模型为序列化做准备似乎是不可取的

然而,仅仅因为数据的对象模型,我就人为地夸大了序列化数据的大小,这让我感到很不舒服


是否有更好的序列化参数或库选择来序列化这种类型的对象图?我确实尝试在应用于列表的
ProtoMember
属性上设置
DataFormat=DataFormat.Group
,但看到消息大小的0变化,这让我感到困惑。

protobuf net中没有任何东西能够神奇地重新排列对象模型以利用特定功能;这需要对数据有详细的了解,这对人类来说是显而易见的,但很难概括。如果不投入大量时间,这里的答案很简单:它将按照模型中的布局对其进行序列化——如果这不是一个完美的场景,那就这样吧


至于
数据格式没有帮助:分组的子消息只适用于
列表
;由于字段号为
11
,它保证需要2个字节的开销:1个字节用于起始组标记,1个字节用于结束组标记。另一种选择是长度前缀,字段头需要1个字节,子消息长度需要一个变量字节数,编码为
varint
。如果每个子消息小于128字节,这仍然只需要一个字节来编码长度(总共2个字节)-这可能就是它没有任何区别的原因:每个单独的
OcrCharacter
都足够小(小于128字节),以至于
Group
帮不上忙。

,我很感兴趣——编写某种自动转发器并不是不可能的,它通过索引将属性映射到通过位置“压缩”的元素。我能在这方面多想一想,然后再打给你吗?对于
OcrCharacter
我想我可以让它工作-基本上,规则是要启用压缩转置,类型上的所有成员必须是同一类型,并且该类型必须是与压缩编码兼容的原语;字段上指定的字段号将映射到转置序列中基于1的位置”-听起来合理吗?我还想添加“成员的字段号必须是连续的”。这似乎可以很好地描述您的
OcrCharacter
类型。想法?另外:它需要是可选的(opt-in)-也许是
[ProtoContract]
上的一些新的
transpospacked=true
…这行得通吗?见鬼,行得通:)很高兴你感兴趣。我不认为这是一个太空泛的想法…我想人们肯定会序列化很多整数
对象或类似的对象。这取决于数据(比如我的情况)压缩位编码具有出色的性能(在数据中大量运行)我不想错过它。至于类型限制,你会知道你想要的实现需要什么。我在想,包含类基本上会在原始类的每个字段中提取一个列表…这将最大限度地提高运行的可能性,从而获得最大的压缩位好处。但不确定为什么它们会eed应该是类似的类型,甚至是一个基本类型(有些类型不会打包,但是很好)。如果我手动实现它,这就是我的想法。您可能有一些非常不同的想法,事实上您提到的限制在我的情况下也会起作用。感谢
组的解释,这很有意义。这也回答了我关于协议缓冲区(和protobuf net)中可用选项的问题。我同意,如果没有领域知识或一些奇特的启发,序列化程序无法知道哪些优化是完美的,但它可以公开一些选项。我认为这种情况是序列化程序只是改变了它存储非常简单的结构化数据的方式,这与现有的引用跟踪或packedbits选项相当。
[ProtoContract(SkipConstructor = true, UseProtoMembersOnly = true)]
public class OcrTableCell
{
    [ProtoMember(1)]
    public int Row;

    //...

    [ProtoMember(10)]
    public int Height;

    [ProtoMember(11, IsPacked=true)]
    public List<int> CharacterCode;

    [ProtoMember(12, IsPacked=true)]
    public List<int> CharacterData;

    //...

    [ProtoMember(21, IsPacked=true)]
    public List<int> CharacterWidth;
}