使用 protobuf-net 进行枚举序列化

Posted

技术标签:

【中文标题】使用 protobuf-net 进行枚举序列化【英文标题】:enum serialization with protobuf-net 【发布时间】:2013-05-28 23:36:39 【问题描述】:

我在一个大型项目中将旧版本的 protobuf 更新为当前版本(使用的版本大约有 1-2 年的历史。我不知道版本)。 可惜新版本抛出异常

ProtoReader.cs 第 292 行中的 CreateWireTypeException

在以下测试用例中:

    enum Test
    
        test1 = 0,
        test2
    ;
    static public void Test1()
    
        Test original = Test.test2;
        using (MemoryStream ms = new MemoryStream())
        
            Serializer.SerializeWithLengthPrefix<Test>(ms, original, PrefixStyle.Fixed32, 1);
            ms.Position = 0;
            Test obj;
            obj = Serializer.DeserializeWithLengthPrefix<Test>(ms, PrefixStyle.Fixed32);
        
    

我发现枚举不应该直接在类之外进行序列化,但是我们的系统太大而无法简单地将所有枚举包装在类中。这个问题还有其他解决方案吗?它适用于序列化和反序列化,只有DeserializeWithLengthPrefix 会引发异常。

测试用例在旧版本中运行良好,例如protobuf-net 的 r262。

【问题讨论】:

【参考方案1】:

简单地说,一个错误;这已在 r640 中修复(现在部署到 NuGet 和 google 代码),以及基于您上面的代码的额外测试,以便它不会重新进入。


重新性能(cmets);我要查看的 first 提示是:“首选组”。基本上,protobuf 规范包括 2 种不同的方式来包含子对象——“组”和“长度前缀”。群组是原始实现,但谷歌现在已经转向“长度前缀”,并尝试建议人们不要使用“群组”。然而!由于 protobuf-net 的工作方式,“组”实际上写起来要便宜得多。这是因为与 google 实现不同,protobuf-net 提前知道事物的长度。这意味着要编写长度前缀,它需要执行以下操作之一:

根据需要计算长度(几乎与实际序列化数据的工作量一样多,bud 添加代码的完整副本);写入长度,然后实际序列化数据 序列化到缓冲区,写入长度,写入缓冲区 留下一个占位符,序列化,然后循环返回并将实际长度写入占位符,如果需要调整填充

我在不同时间实现了所有 3 种方法,但 v2 使用第 3 个选项。我一直在尝试添加第四个实现:

留下一个占位符,序列化,然后循环返回并写入实际长度使用超长形式(因此无需调整填充)

但是……共识似乎是“超长形式”有点冒险;尽管如此,protobuf-net 到 protobuf-net 仍然可以很好地工作。

但正如您所见:长度前缀总是有一些开销。现在想象一下嵌套相当深的对象,你可以看到一些光点。小组的工作方式非常不同;组的编码格式为:

写一个开始标记;连载;写一个结束标记

就是这样;不需要长度;真的,真的,真的很便宜写。在电线上,它们之间的主要区别是:

groups:写起来很便宜,但是如果遇到意外数据就不能跳过它们;您必须解析有效负载的标头 长度前缀:写入成本更高,但如果您遇到意外数据时跳过它们的成本较低 - 您只需读取长度并复制/移动那么多字节

但是!太详细了!

这对你意味着什么?好吧,假设你有:

[ProtoContract]
public class SomeWrapper

    [ProtoMember(1)]
    public List<Person> People  get  return people;  

    private readonly List<Person> people = new List<Person>();

您可以进行超级复杂的更改:

[ProtoContract]
public class SomeWrapper

    [ProtoMember(1, DataFormat=DataFormat.Group)]
    public List<Person> People  get  return people;  

    private readonly List<Person> people = new List<Person>();

它会使用更便宜的编码方案。只要您使用 protobuf-net,您现有的所有数据都可以。

【讨论】:

谢谢 :) 现在很好用。而且似乎新版本比旧版本快得多。做得好! :) 顺便说一句。您对优化 proto-buf 以获得最大速度有什么建议吗?我在 .net 服务器上工作,序列化在我们的分析数据中仍然遥遥领先,每一毫秒都值得我们修复。 @luz 这将取决于模型;对子对象使用“组”格式是一个很大的帮助——它避免了缓冲或修复;并且就 protobuf-net 而言,两者之间的切换不是重大更改 - 任何旧数据仍然可以正常工作(但其他实现可能不会那么宽容)。我还要问:您的应用程序是“启动,做一件事,关闭”,还是“启动,运行很长时间,做很多事情”?我问的原因是 default 是在需要时进行元编程,这会为每种类型的 first 使用增加几毫秒,但是 @luz 实际上可以使用一些在独立 dll 中无效的技巧,因此实际上是最快的整体性能;但是,如果您想最大化 启动 速度(但接受它在执行时几乎没有明显慢),您可以将序列化程序代码预先生成到单独的程序集中.添加此功能主要是为了帮助支持无法进行元编程的“电话、winrt 等”。但它也是常规应用程序的一个选项。 感谢您的建议。 :) 我会尽快尝试。我的应用程序是一个服务器并且永远运行,所以启动不是一个重要的因素

以上是关于使用 protobuf-net 进行枚举序列化的主要内容,如果未能解决你的问题,请参考以下文章

使用带有标志枚举的 ProtoBuf-Net 时出错

你如何序列化和反序列化枚举?

Protobuf-net 对字节数组进行序列化/反序列化

使用 ProtoBuf-Net 进行两组不同的序列化

Unity使用protobuf-net进行二进制序列化与反序列化

我可以使用 Protobuf-net 对已用 Java 序列化的数据进行反序列化吗?