在这里让我们分别使用PB与JSON的序列化与反序列化能力,对值完全相同的一份结构化数据进行不同次数的性能测试。
为了可读性,下面这一份文本使用JSON格式展示了需要被进行测试的结构化数据内容:
{
"age" : 20,
"name" : "张珊",
"phone" :
[
{
"number" : "110112119",
"type" : 0
},
{
"number" : "110112119",
"type" : 0
},
{
"number" : "110112119",
"type" : 0
},
{
"number" : "110112119",
"type" : 0
},
{
"number" : "110112119",
"type" : 0
}
],
"qq" : "95991122",
"address" :
{
"home_address" : "陕西省西安市⻓安区",
"unit_address" : "陕西省西安市雁塔区"
},
"remark" :
{
"key1" : "value1",
"key2" : "value2",
"key3" : "value3",
"key4" : "value4",
"key5" : "value5"
}
}
开始进行测试代码编写,我们在新的目录下新建contacts. proto文件,内容如下:
syntax="proto3";
package compare_serialization;
import "google/protobuf/any.proto"; // 引⼊ any.proto ⽂件
// 地址
message Address{
string home_address = 1; // 家庭地址
string unit_address = 2; // 单位地址
}
// 联系⼈
message PeopleInfo {
string name = 1; // 姓名
int32 age = 2; // 年龄
message Phone {
string number = 1; // 电话号码
enum PhoneType {
MP = 0; // 移动电话
TEL = 1; // 固定电话
}
PhoneType type = 2; // 类型
}
repeated Phone phone = 3; // 电话
google.protobuf.Any data = 4;
oneof other_contact { // 其他联系⽅式:多选⼀
string qq = 5;
string weixin = 6;
}
map remark = 7; // 备注
}
使用protoc命令编译文件后,新建性能测试文件compare.cc,我们分别对相同的结构化数据进行
100、1000、 10000 、100000 次的序列化与反序列化,分别获取其耗时与序列化后的大小。
内容如下:
#include
#include
#include
#include "contacts.pb.h"
using namespace std;
using namespace compare_serialization;
using namespace google::protobuf;
#define TEST_COUNT 100000
void createPeopleInfoFromPb(PeopleInfo *people_info_ptr);
void createPeopleInfoFromJson(Json::Value& root);
int main(int argc, char *argv[])
{
struct timeval t_start,t_end;
double time_used;
int count;
string pb_str, json_str;
// ------------------------------Protobuf 序列化------------------------------------
{
PeopleInfo pb_people;
createPeopleInfoFromPb(&pb_people);
count = TEST_COUNT;
gettimeofday(&t_start, NULL);
// 序列化count次
while ((count--) > 0) {
pb_people.SerializeToString(&pb_str);
}
gettimeofday(&t_end, NULL);
time_used=1000000*(t_end.tv_sec - t_start.tv_sec) + t_end.tv_usec - t_start.tv_usec;
cout << TEST_COUNT << "次 [pb序列化]耗时:" << time_used/1000 << "ms."
<< " 序列化后的大小:" << pb_str.length() << endl;
}
// ------------------------------Protobuf 反序列化------------------------------------
{
PeopleInfo pb_people;
count = TEST_COUNT;
gettimeofday(&t_start, NULL);
// 反序列化count次
while ((count--) > 0) {
pb_people.ParseFromString(pb_str);
}
gettimeofday(&t_end, NULL);
time_used=1000000*(t_end.tv_sec - t_start.tv_sec) + t_end.tv_usec - t_start.tv_usec;
cout << TEST_COUNT << "次 [pb反序列化]耗时:" << time_used / 1000 << "ms." << endl;
}
// ------------------------------JSON 序列化------------------------------------
{
Json::Value json_people;
createPeopleInfoFromJson(json_people);
Json::StreamWriterBuilder builder;
count = TEST_COUNT;
gettimeofday(&t_start, NULL);
// 序列化count次
while ((count--) > 0) {
json_str = Json::writeString(builder, json_people);
}
gettimeofday(&t_end, NULL);
// 打印序列化结果
// cout << "json: " << endl << json_str << endl;
time_used=1000000*(t_end.tv_sec - t_start.tv_sec) + t_end.tv_usec - t_start.tv_usec;
cout << TEST_COUNT << "次 [json序列化]耗时:" << time_used/1000 << "ms."
<< " 序列化后的大小:" << json_str.length() << endl;
}
// ------------------------------JSON 反序列化------------------------------------
{
Json::CharReaderBuilder builder;
unique_ptr reader(builder.newCharReader());
Json::Value json_people;
count = TEST_COUNT;
gettimeofday(&t_start, NULL);
// 反序列化count次
while ((count--) > 0) {
reader->parse(json_str.c_str(), json_str.c_str() + json_str.length(), &json_people, nullptr);
}
gettimeofday(&t_end, NULL);
time_used=1000000*(t_end.tv_sec - t_start.tv_sec) + t_end.tv_usec - t_start.tv_usec;
cout << TEST_COUNT << "次 [json反序列化]耗时:" << time_used/1000 << "ms." << endl;
}
return 0;
}
/**
* 构造pb对象
*/
void createPeopleInfoFromPb(PeopleInfo *people_info_ptr)
{
people_info_ptr->set_name("张珊");
people_info_ptr->set_age(20);
people_info_ptr->set_qq("95991122");
for(int i = 0; i < 5; i++) {
PeopleInfo_Phone* phone = people_info_ptr->add_phone();
phone->set_number("110112119");
phone->set_type(PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
}
Address address;
address.set_home_address("陕西省西安市长安区");
address.set_unit_address("陕西省西安市雁塔区");
google::protobuf::Any * data = people_info_ptr->mutable_data();
data->PackFrom(address);
people_info_ptr->mutable_remark()->insert({"key1", "value1"});
people_info_ptr->mutable_remark()->insert({"key2", "value2"});
people_info_ptr->mutable_remark()->insert({"key3", "value3"});
people_info_ptr->mutable_remark()->insert({"key4", "value4"});
people_info_ptr->mutable_remark()->insert({"key5", "value5"});
}
/**
* 构造json对象
*/
void createPeopleInfoFromJson(Json::Value& root) {
root["name"] = "张珊";
root["age"] = 20;
root["qq"] = "95991122";
for(int i = 0; i < 5; i++) {
Json::Value phone;
phone["number"] = "110112119";
phone["type"] = 0;
root["phone"].append(phone);
}
Json::Value address;
address["home_address"] = "陕西省西安市长安区";
address["unit_address"] = "陕西省西安市雁塔区";
root["address"] = address;
Json::Value remark;
remark["key1"] = "value1";
remark["key2"] = "value2";
remark["key3"] = "value3";
remark["key4"] = "value4";
remark["key5"] = "value5";
root["remark"] = remark;
}
makefile:
compare:*.cc
g++ -o $@ $^ -std=c++11 -lprotobuf
.PHONY:clean
clean:
rm -rf compare
测试结果如下:
100次 [pb序列化]耗时:0.382ms. 序列化后的大小:278
100次 [pb反序列化]耗时:0.442ms.
100次 [json序列化]耗时:2.43ms. 序列化后的大小:567
100次 [json反序列化]耗时:1.091ms.1000次 [pb序列化]耗时:3.196ms. 序列化后的大小:278
1000次 [pb反序列化]耗时:5.047ms.
1000次 [json序列化]耗时:20.22ms. 序列化后的大小:567
1000次 [json反序列化]耗时:13.037ms.10000次 [pb序列化]耗时:29.206ms. 序列化后的大小:278
10000次 [pb反序列化]耗时:48.03ms.
10000次 [json序列化]耗时:206.259ms. 序列化后的大小:567
10000次 [json反序列化]耗时:114.738ms.
由实验结果可得:
●编解码性能: ProtoBuf 的编码解码性能,比JSON高出2-4倍。
●内存占用: ProtoBuf的内存278,而JSON到达567, ProtoBuf的内存占用只有JSON的1/2。
注:以上结论的数据只是根据该项实验得出。因为受不同的字段类型、字段个数等影响,测出的数据会有所差异。
小结:
1. XML、JSON、 ProtoBuf 都具有数据结构化和数据序列化的能力。
2. XML、JSON更注重数据结构化,关注可读性和语义表达能力。ProtoBuf 更注重数据序列化,关注效率、空间、速度,可读性差,语义表达能力不足,为保证极致的效率,会舍弃一部分元信息。
3. ProtoBuf 的应用场景更为明确,XML、JSON的应用场景更为丰富。