Protobuf的使用及性能对比测试

Protobuf简介

Protocal Buffers是谷歌开发的一种数据描述语言,能够将结构化数据序列化,可用于数据存储、通信协议等方面。Protobuf是跨语言的,并且自带了一个编译器(protoc),只需要用它进行编译,可以编译成Java、python、C++、C#、Go等代码,然后就可以直接使用,不需要再写其他代码,自带有解析的代码。

因此Protobuf的使用非常方便,并且还有一个重要的特点:它比Json格式更快,且占用空间更小。

它的官方网址为:https://github.com/protocolbuffers/protobuf

Protobuf的安装

Protobuf的安装总共分为两步:

  • 安装Protobuf的自带编译器:protoc
  • 安装对应语言版本的客户端
Step1:安装Protobuf自带的编译器

Protobuf自带编译器的安装可以自己在本机上通过源代码编译安装,也可以直接下载官方相应的编译好的二进制包,开箱即用。官方编译好的包下载地址为:https://github.com/protocolbuffers/protobuf/releases/tag/v3.7.1

1

如上图所示,这个页面有各种语言的protobuf包进行下载,分别表示如下:

  • all表示所有语言的包,后面cpp表示C++语言,csharp表示C#语言
  • 各语言包之后是各平台编译的二进制包,对应有Linux、MacOSWindows操作系统的编译结果
  • 最后是源码

这里如果下载了相应语言的protobuf包,需要通过编译安装方式进行安装,以Python为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 下载相应的python版本:protobuf-python-3.7.1.zip
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/protobuf-python-3.7.1.zip
# 解压缩
unzip protobuf-python-3.7.1.zip
cd protobuf-3.7.1
# 编译安装protoc
./configure
sudo make && sudo make install
# 安装python语言客户端
cd python
python setup.py build --cpp_implementation
python setup.py test --cpp_implementation
python setup.py install --cpp_implementation

如果上面编译报错的话,可能需要升级gcc的版本:

1
2
3
4
yum install centos-release-scl -y
yum install devtoolset-7 -y
scl enable devtoolset-7 bash
gcc --version

同时还需要注意的是,上面实际上既编译安装了protobuf的编译器protoc,又安装了protobuf的python语言客户端

还有一点非常重要:安装python语言客户端的时候,如果没有指定--cpp_implementation这个参数,那么安装的是纯python版本的解析器,这个解析速度并不快,甚至比json还要慢,因此安装的时候一定要带这个参数,相当于是以C++的运行时来解析protobuf格式的数据,会比纯python方式快10倍以上。

如果这里不想自己编译,那就直接下载官方编译好的包:

1
2
3
4
5
6
7
8
9
10
# 下载官方编译好的包
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.7.1/protoc-3.7.1-linux-x86_64.zip
# 新建目录
mkdir protobuf
# 解压到指定目录
unzip protoc-3.7.1-linux-x86_64.zip -d protobuf
# 进入目录
cd protobuf/bin
# 查看版本
./protoc --version

此时,进入bin目录下就可以看到已经编译好的protoc可执行文件了。

Step2:安装Protobuf语言客户端

安装python的客户端,直接用pip命令即可安装:

1
sudo /usr/local/bin/pip3 install protobuf

这里通过pip命令安装默认是加了--cpp_implementationC++运行时参数的,注意仅在protobuf 3.2及以上版本是默认加的,其他旧版本是纯python的,没有加该参数进行优化。

Protobuf的使用

protobuf 2protobuf 3的语法相差比较大,具体可参考这篇文章。下面以protobuf 3为例进行说明。

新建proto文件

proto文件是用来指定数据格式的,示例addressbook.proto如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
syntax = "proto3";

package tutorial;

message Person {
string name = 1;
int32 id = 2;
string email = 3;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}

message PhoneNumber {
string number = 1;
PhoneType type = 2;
}

PhoneNumber phones = 4;
}
用protoc编译器生成对应的语言类

输入如下内容,可以看到如下结果:

1
protoc --help

2

也就是说通过这个二进制的可执行命令可以将前面定义的数据格式addressbook.proto生成各种语言的引用包。

1
2
# 在当前目录下生成addressbook.proto对应的python格式的引用文件
protoc --python_out=. addressbook.proto

可以看到在当前文件夹下面生成了一个addressbook_pb2.py文件,后面在python程序中引用这个文件,就可以进行相应的序列化和反序列化操作了。

需要注意的是,protoc编译器的版本与相应语言解析protobuf的客户端版本最好一致,不然会有一些莫名的bug

Protobuf与Json性能的比较

性能比较采用的工具是line_profiler,它可以逐行打印函数每一行运行所花费的时间。

安装该工具:

1
sudo /usr/local/bin/pip3 install line_profiler

然后写测试脚本test.py如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import json

import addressbook_pb2

# 对这个函数进行性能测试,在前面加一个装饰器
@profile
def parse_json(input_data):
# json序列化
data_json = json.dumps(input_data)
# json反序列化
result = json.loads(data_json)
return result

@profile
def parse_protobuf(input_data):
# protobuf序列化
data_protobuf = input_data.SerializeToString()
# protobuf反序列化
result_protobuf.ParseFromString(data_protobuf)
return result_protobuf


if __name__ == "__main__":
# 构造json格式数据
data = {
"name": "xiaowang",
"id": 1,
"email": "123@qq.com",
"phones": {
"number": "156888888"
}
}
# 构造protobuf格式数据
person = addressbook_pb2.Person()
person.name = data["name"]
person.id = data["id"]
person.email = data["email"]
person.phones.number = data["phones"]["number"]
person.phones.type = 0
# 初始化解析protobuf解析类
result_protobuf = addressbook_pb2.Person()
# 每个函数执行1000次
for number in range(1000):
parse_json(data)
parse_protobuf(person)

输入如下命令执行该脚本:

1
kernprof -l -v test.py

得到结果如下:

3

可以看到无论是序列化还是反序列化,protobuf都比json5倍左右。

Protobuf使用注意事项

如果定义了两个proto文件,里面写的是同样的package,且有同样的变量名,那么在python同时引用这两个pb2.py文件的时候会报错。

例如再新建一个addressbook1.proto文件,内容如下:

1
2
3
4
5
6
7
8
9
syntax = "proto3";

package tutorial;

message Person {
string name = 1;
int32 id = 2;
string email = 3;
}

同样

1
protoc --python_out=.  addressbook1.proto

生成一个addressbook1_pb2.py文件。在python中同时引用这两个文件,会报错如下:

4

解决办法是:如果有两个proto文件里面有相同的变量名,在package里面写不一样的包名即可。这样重新生成的pb2.py文件,再同时引用的时候就不会报错了:

5


【版权声明】
本文首发于戚名钰的博客,欢迎转载,但是必须保留本文的署名戚名钰(包含链接)。如您有任何商业合作或者授权方面的协商,请给我留言:qimingyu.security@foxmail.com
欢迎关注我的微信公众号:科技锐新

kejiruixin

本文永久链接:http://qimingyu.github.io/2019/05/02/Protobuf的使用及性能对比测试/

坚持原创技术分享,您的支持将鼓励我继续创作!

热评文章