3

我正在将我的一些 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's),我认为打包位序列化的好处会有所帮助,但在当前的类结构中,所有实际列表都是自定义类型。

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

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

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

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

仍然让我感到困扰的是,我仅仅因为数据的对象模型而人为地夸大了序列化数据大小。

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

4

1 回答 1

2

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

至于Group数据格式无济于事:分组子消息仅适用于List<OcrCharacter>; 由于 field-number 是11,它保证需要 2 个字节的开销:1 个字节用于起始组标记,1 个字节用于结束组标记。另一种方法是长度前缀,字段标头需要 1 个字节,子消息的长度需要可变varint数量的字节,编码为. 如果每个子消息小于 128 个字节,这仍然只需要一个字节来编码长度(所以总共 2 个字节) - 这可能就是它没有任何区别的原因:每个人OcrCharacter都足够小(小于 128字节),这Group无济于事。

于 2013-06-07T22:52:15.793 回答