.Net中使用protobuf序列化:Google.Protobuf vs protobuf-net
博客分类: 示例
什么是protobuf?
protobuf全称Protocol Buffers,由Google推出的一种平台、语言无关的数据交互格式。
为什么用protobuf
由于公司项目开发中,使用了WCF,其默认数据序列化是DataContractSerializer,采用xml格式,考虑到性能提升,于是采用了protobuf。
比较选择
protobuf的.net实现主要有两个版本:
- Google.Protobuf
- Google官方维护,项目前身是protobuf-csharp-port。
- 优势:在多平台协作开发时,定义一份协议,可以在各个语言中共用。
- protobuf-net
- 社区维护,主要由Marc Gravell管理维护
- 优势:相对来说,更符合.net开发习惯;对于纯粹的.NET程序,使用起来更加方便
Google.ProtoBuf 如何使用
- 安装Nuget包:Google.Protobuf和Google.Protobuf.Tools
``` xml
<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中的类结构描述
``` protobuf
syntax = "proto3";
package Jess.Sample.GoogleProtoBuf;
message Test {
int32 ID = 1;
string Name = 2;
}
```
- 基于上面定义的proto文件,生成相应的C#类文件
命令行中执行如下代码:
``` bash
<自己的protoc.exe文件目录>protoc.exe --proto_path=<proto文件所在目录> --csharp_out=<proto生成的cs文件目录> <定义的proto文件名>
```
或者参考如下powershell脚本内容:
将脚本内容保存到项目目录下,直接执行ps1文件。
``` powershell
# 跳转到当前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
```
- 编写序列化相关代码
``` csharp
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
``` xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="protobuf-net" Version="3.0.29" />
</ItemGroup>
</Project>
```
- 编写待序列化的类
- protobuf-net作者同时提供了Protogen工具,可以直接使用.proto文件生成以下类。
``` csharp
using ProtoBuf;
namespace Jess.Sample.ProtoBufNet
{
[ProtoContract]
public class Test
{
[ProtoMember(1)]
public int ID { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
}
}
```
- 实现序列化方法
``` csharp
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 —— 从性能数据上看,也是无差别的。
``` ini
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序列化
``` ini
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反序列化
``` ini
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序列化
``` ini
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反序列化
``` ini
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。
掘:奇葩史