c# - 具有大量非原始类型列表的 Protocol Buffer 序列化

标签 c# serialization protocol-buffers protobuf-net

我正在将我的一些 DataContractSerializer 使用切换到 Protocol Buffer 序列化(特别是使用 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 中的每个字段一个.这对序列化数据大小有相当大的影响,在某些情况下减少了三分之二(即使在 gzip 压缩之后)。

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

仍然让我感到困扰的是,我有一个人为膨胀的序列化数据大小只是因为数据的对象模型。

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

最佳答案

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

至于Group数据格式无济于事:分组的子消息仅适用于 List<OcrCharacter> 之类的东西;因为字段号是 11 ,它保证需要 2 个字节的开销:1 个字节用于起始组标记,1 个字节用于结束组标记。替代方案是长度前缀,需要 1 个字节用于字段 header ,以及一个变量 字节数作为子消息的长度,编码为varint .如果每条子消息小于 128 字节,这仍然只需要一个字节来编码长度(因此总共 2 个字节)——这可能是它没有任何区别的原因:每个个体 OcrCharacter Group 足够小(小于 128 字节)帮不上忙。

关于c# - 具有大量非原始类型列表的 Protocol Buffer 序列化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16993940/

相关文章:

c# - 使用 Task.ContinueWith 时如何避免嵌套的 AggregateException?

c# - 从 ImageList 中获取的图像看起来与直接从资源中获取的图像不同

c# - 如何堆叠日志消息并在发生异常时记录它们?

c# - 带有 XmlTypeMapping 和 XmlRootAttribute 参数的 XmlSerializer 构造函数

reverse-engineering - 在没有 .proto 文件的情况下解析 Protocol-Buffers

web - 如何将 webpack 指向特定的 node_modules 文件夹

c++ - 如何在 Protobuf 中声明 unsigned long long?

c# - 对 sqlcommand.executescalar 使用声明

java - 如何强制 CXF 序列化所有自动生成的客户端类?

使用 Protocol Buffer 进行 Java 序列化