protocol-buffers序列化,包含大量非基本类型

时间:2013-06-07 22:36:29

标签: c# serialization protocol-buffers protobuf-net

我将我的一些DataContractSerializer用法切换到协议缓冲区序列化(特别是使用protobuf-net),目标是更快的序列化和更小的序列化数据大小,以便存储在数据库blob中。

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

具体来说,我的问题是:我可以更改我的protobuf-net使用情况,或者可能更改序列化库,以获得更小的邮件大小?我将给出一个对象模型以及到目前为止我能够弄清楚的内容。

在我的情况下,我正在序列化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;
}

由于数据最终只是一堆相关的原语(大多数是int),我认为打包位序列化的好处会有所帮助,但在当前的类结构中,所有实际的列表都是自定义类型。

为了允许打包位序列化,我完全删除了自定义类型,并且有多个基元列表,它们按顺序相关联。例如:

[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;
}

在这里,您可以看到我将List<OcrCharacter>替换为多个列表:OcrCharacter中的每个字段都有一个列表。这对序列化数据大小有相当大的影响,在某些情况下减少了三分之二(即使在gzipping之后)。

我认为对我的对象模型进行这样的更改只是为了支持序列化是不切实际的......并且保留第二个“帮助器”模型以准备序列化似乎是不可取的。

由于数据的对象模型,我有一个人为膨胀的序列化数据大小,这仍然让我感到困惑。

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

1 个答案:

答案 0 :(得分:2)

protobuf-net内部没有任何东西可以用魔法重新排列你的对象模型来利用特定功能;这需要详细的数据知识,这对人类来说是显而易见的,但很难概括。如果不投入大量时间,这里的答案很简单:它将序列化,因为它在模型中布局 - 如果这不是完美的场景:那就这样吧。

至于Group数据格式无效:分组子消息仅适用于List<OcrCharacter>等内容;由于字段编号为11,因此它保证需要2个字节的开销:起始组标记为1个字节,端组标记为1个字节。替代方案是长度前缀,对于字段标头需要1个字节,对于子消息的长度需要 变量 字节数,编码为varint。如果每个子消息小于128个字节,这仍然只需要一个字节来编码长度(因此总共2个字节) - 这可能就是为什么它没有任何区别:每个人OcrCharacter都很小足够(少于128个字节),Group无法帮助。