.Net中使用protobuf序列化:Google.Protobuf vs protobuf-net

旧地址:https://www.shisujie.com/blog/Google-ProtoBuf-vs-ProtoBuf-Net

Protocol Buffers - Google官方教程文档
示例源代码

什么是protobuf?

protobuf全称Protocol Buffers,由Google推出的一种平台、语言无关的数据交互格式。

为什么用protobuf

由于公司项目开发中,使用了WCF,其默认数据序列化是DataContractSerializer,采用xml格式,考虑到性能提升,于是采用了protobuf。

比较选择

protobuf的.net实现主要有两个版本:

  • Google.Protobuf Google.Protobuf
    • Google官方维护,项目前身是protobuf-csharp-port
    • 优势:在多平台协作开发时,定义一份协议,可以在各个语言中共用。
  • protobuf-net protobuf-net
    • 社区维护,主要由Marc Gravell管理维护
    • 优势:相对来说,更符合.net开发习惯;对于纯粹的.NET程序,使用起来更加方便

Google.ProtoBuf 如何使用

  • 安装Nuget包:Google.Protobuf和Google.Protobuf.Tools
1
2
3
4
5
6
7
8
9
10
11

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.12.3" />
<PackageReference Include="Google.Protobuf.Tools" Version="3.12.3" />
</ItemGroup>
</Project>

  • 定义proto协议描述文件(.proto)

类似于.net中的类结构描述

1
2
3
4
5
6
7
8
9
10

syntax = "proto3";

package Jess.Sample.GoogleProtoBuf;

message Test {
int32 ID = 1;
string Name = 2;
}

  • 基于上面定义的proto文件,生成相应的C#类文件

命令行中执行如下代码:

1
2
3

<自己的protoc.exe文件目录>protoc.exe --proto_path=<proto文件所在目录> --csharp_out=<proto生成的cs文件目录> <定义的proto文件名>

或者参考如下powershell脚本内容

将脚本内容保存到项目目录下,直接执行ps1文件。

1
2
3
4
5
6
7
8
9
10

# 跳转到当前powershell脚本文件目录
$scriptpath = $PSScriptRoot
cd $scriptpath

# 获取用户目录
$userpath=$env:USERPROFILE
# 执行protoc生成类
&$userpath'\.nuget\packages\google.protobuf.tools\3.12.3\tools\windows_x64\protoc.exe' --proto_path=./protofile --csharp_out=./protoclass test.proto

  • 编写序列化相关代码
1
2
3
4
5
6
7
8
9
10
11
12
13

public void SimplestUse()
{
Test test_forProtobuf = new Test();
test_forProtobuf.ID = 2;
test_forProtobuf.Name = "测试 Google.ProtoBuf";

using (MemoryStream memoryStream = new MemoryStream())
{
test_forProtobuf.WriteTo(memoryStream);
}
}

更多使用示例,见本文开头源码链接。


protobuf-net 如何使用

  • 安装Nuget包:protobuf-net
1
2
3
4
5
6
7
8
9
10

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="protobuf-net" Version="3.0.29" />
</ItemGroup>
</Project>

  • 编写待序列化的类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

using ProtoBuf;

namespace Jess.Sample.ProtoBufNet
{
[ProtoContract]
public class Test
{
[ProtoMember(1)]
public int ID { get; set; }

[ProtoMember(2)]
public string Name { get; set; }
}
}

  • 实现序列化方法
1
2
3
4
5
6
7
8
9
10
11
12
13

public void SimplestUse()
{
Test test_forProtobuf = new Test();
test_forProtobuf.ID = 1;
test_forProtobuf.Name = "测试 protobuf-net";

using (MemoryStream memoryStream = new MemoryStream())
{
ProtoBuf.Serializer.Serialize(memoryStream, test_forProtobuf);
}
}

更多使用示例,见本文开头源码链接。

Google.ProtoBuf 和 protobuf-net 性能比较

性能测试工具使用:BenchmarkDotNet

  • MemoryStream序列化

直接传入MemoryStream的WriteTo扩展方法,其实是官方内部封装了CodedOutputStream —— 从性能数据上看,也是无差别的。

1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Median Rank
Google.ProtoBuf使用MemoryStream 789.7 ns 15.72 ns 46.36 ns 775.3 ns 2
Google.ProtoBuf使用MemoryStream嵌套CodedOutputStream 799.6 ns 16.14 ns 46.81 ns 791.6 ns 2
protobuf-net使用MemoryStream 491.7 ns 7.13 ns 6.32 ns 488.8 ns 1
  • FileStream序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 460.3 μs 14.65 μs 43.18 μs 1
protobuf-net使用FileStream 457.9 μs 15.37 μs 45.31 μs 1
  • FileStream反序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 249.8 μs 4.97 μs 10.15 μs 1
protobuf-net使用FileStream 260.5 μs 4.84 μs 9.88 μs 2

上方是简易数据结构性能,下方是相对复杂一些的数据结构性能


注意事项【一些坑】:

  • proto3移除了required,对于required的作用,其实争议很大。proto3更加追求极简。
  • 但在我的之前遇到的情况,bool和enum未赋值的情况下,序列化时,默认值是有问题的。所以,单纯.net环境下,目前还是推荐proto2吧。
  • _:proto文件中下划线表示下划线后一个字母为大写。生成的类中,属性是无法带有下划线的。
  • 官方建议属性定义均用小写
  • FileStream序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 470.3 μs 17.45 μs 51.47 μs 1
protobuf-net使用FileStream 466.9 μs 14.68 μs 43.28 μs 1
  • FileStream反序列化
1
2
3
4
5
6
7

BenchmarkDotNet=v0.12.1, OS=Windows 10.0.18362.900 (1903/May2019Update/19H1)
Intel Core i7-7700 CPU 3.60GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
[Host] : .NET Framework 4.8 (4.8.4180.0), X86 LegacyJIT

Toolchain=InProcessEmitToolchain

Method Mean Error StdDev Rank
Google.ProtoBuf使用FileStream 281.3 μs 7.42 μs 21.88 μs 1
protobuf-net使用FileStream 283.1 μs 5.64 μs 13.29 μs 1

由此可见,两个库直接性能差距并不是很大,鉴于个人的使用场景,更加推荐protobuf-net。


掘:奇葩史