ProtoBuf 语法(三)

news/2024/11/30 8:31:34/

系列文章
ProtoBuf 语法(一)
ProtoBuf 语法(二)


文章目录

  • 九、option 选项
    • 9.1 选项分类
    • 9.2 常用选项
  • 十、ProtoBuf 与 JSON 的性能对比
    • 10.1 序列化能力对比
    • 10.2 总结

九、option 选项

.proto文件中可以声明许多选项,使用 option 标注。选项能影响proto编译器的某些处理方式。

9.1 选项分类

选项的完整列表在google/protobuf/descriptor.proto中定义。部分代码如下:

syntax = "proto2"; // descriptor.proto 使⽤ proto2 语法版本
message FileOptions { ... } // ⽂件选项 定义在 FileOptions 消息中
message MessageOptions { ... } // 消息类型选项 定义在 MessageOptions 消息中
message FieldOptions { ... } // 消息字段选项 定义在 FieldOptions 消息中
message OneofOptions { ... } // oneof字段选项 定义在 OneofOptions 消息中
message EnumOptions { ... } // 枚举类型选项 定义在 EnumOptions 消息中
message EnumValueOptions { .. } // 枚举值选项 定义在 EnumValueOptions 消息中
message ServiceOptions { ... } // 服务选项 定义在 ServiceOptions 消息中
message MethodOptions { ... } // 服务⽅法选项 定义在 MethodOptions 消息中
//...

由此可见,选项分为 文件级消息级字段级 等等,但并没有⼀种选项能作用于所有的类型。

9.2 常用选项

  1. optimize_for:该选项为文件选项,可以设置protoc编译器的优化级别,分别为 SPEEDCODE_SIZELITE_RUNTIME 。受该选项影响,设置不同的优化级别,编译.proto文件后生成的代码内容不同。
  • SPEEDprotoc编译器将生成的代码是⾼度优化的,代码运行效率⾼,但是由此生成的代码编译后会占用更多的空间。 另外,SPEED 是默认选项。
  • CODE_SIZEprotoc编译器将生成最少的类,会占用更少的空间,是依赖基于反射的代码来实现序列化、反序列化和各种其他操作。但和 SPEED 恰恰相反,它的代码运行效率较低。这种方式适合用在包含大量的.proto文件,而并不盲目追求速度的应用中。
  • LITE_RUNTIME:生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲Protocol Buffer提供的反射功能为代价的,仅仅提供encoding+序列化功能,所以我们在链接BP库时仅需链接libprotobuf-lite,而非libprotobuf。这种模式通常用于资源有限的平台,例如移动手机平台中。
option optimize_for = LITE_RUNTIME;
  1. allow_alias:允许将相同的常量值分配给不同的枚举常量,用来定义别名。该选项为枚举选项。举个例子:
enum PhoneType 
{option allow_alias = true;MP = 0;TEL = 1;LANDLINE = 1; // 若不加 option allow_alias = true; 这⼀⾏会编译报错
}

十、ProtoBuf 与 JSON 的性能对比

10.1 序列化能力对比

这里分别使用PBJSON的序列化与反序列化,对值完全相同的⼀份结构化数据进行不同次数的性能测试。为了可读性,下面这⼀份文本使用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<string, string> remark = 7; // 备注
}

使用protoc命令编译文件后,新建性能测试文件compare.cc,分别对相同的结构化数据进行100 、1000 、10000 、100000次的序列化与反序列化,分别获取其耗时与序列化后的大小。

compare.cc文件的内容如下:

#include <iostream>
#include <sys/time.h>
#include <jsoncpp/json/json.h>
#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<Json::CharReader> 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;
}

测试结果如下:

[lhf@localhost test_pb_json]$ ./compare 
100[pb序列化]耗时:0.319ms. 序列化后的⼤⼩:278
100[pb反序列化]耗时:0.445ms.
100[json序列化]耗时:2.031ms. 序列化后的⼤⼩:567[lhf@localhost test_pb_json]$ ./compare 
1000[pb序列化]耗时:3.245ms. 序列化后的⼤⼩:278
1000[pb反序列化]耗时:4.6ms.
1000[json序列化]耗时:19.747ms. 序列化后的⼤⼩:567
1000[json反序列化]耗时:11.42ms.[lhf@localhost test_pb_json]$ ./compare 
10000[pb序列化]耗时:29.65ms. 序列化后的⼤⼩:278
10000[pb反序列化]耗时:45.103ms.
10000[json序列化]耗时:235.906ms. 序列化后的⼤⼩:567
10000[json反序列化]耗时:108.5ms.[lhf@localhost test_pb_json]$ ./compare 
100000[pb序列化]耗时:282.857ms. 序列化后的⼤⼩:278
100000[pb反序列化]耗时:426.645ms.
100000[json序列化]耗时:1898.32ms. 序列化后的⼤⼩:567
100000[json反序列化]耗时:1087.88ms.

由实验结果可得:

  • 编解码性能:ProtoBuf 的编码解码性能,比JSON高出 2 - 4 倍。
  • 内存占用:ProtoBuf的内存占用只有JSON的 1/2 左右。

注意:以上结论的数据只是根据该项实验得出。因为受不同的字段类型、字段个数等影响,测出的数据会有所差异。

10.2 总结

序列化协议通用性格式可读性序列化大小序列化性能适用场景
JSON通用(json、xml已成为多种行业标准的编写工具)文本格式轻量(使用键值对方式,压缩了⼀定的数据空间)web项目。因为浏览器对于JSON数据支持非常好,有很多内建的函数支持。
XML通用文本格式重量(数据冗余,因为需要成对的闭合标签)XML作为⼀种扩展标记语言,衍生出了HTML、RDF/RDFS,它强调数据结构化的能力和可读性。
ProtoBuf独立(Protobuf只是Google公司内部的工具)二进制格式差(只能反序列化后得到真正可读的数据)轻量(比JSON更轻量,传输起来带宽和速度会有优化)适合高性能,对响应速度有要求的数据传输场景。Protobuf比XML、JSON占用内存更小,速度更快。

通过以上表格,我们可以知道:

  1. XML、JSON、ProtoBuf都具有数据结构化和数据序列化的能力。
  2. XML、JSON更注重数据结构化,关注可读性和语义表达能力。ProtoBuf更注重数据序列化,关注效率、空间、速度;而可读性差,语义表达能力不足,为保证极致的效率,会舍弃一部分元信息。
  3. ProtoBuf的应用场景更为明确,XML、JSON的应用场景更为丰富。

http://www.ppmy.cn/news/109090.html

相关文章

《商用密码应用与安全性评估》第四章密码应用安全性评估实施要点4.1密码应用方案设计

4.1设计原则 密码应用方案设计是信息系统密码应用的起点&#xff0c;它直接决定着信息系统的密码应用能否合规、正确、有效地部署实施。 ①总体性原则&#xff1a;密码在信息系统中的应用不是孤立的&#xff0c;必须与信息系统的业务相结合才能发挥作用。 ②科学性原则&#xf…

STM32单片机(三)第三节:GPIO输入

❤️ 专栏简介&#xff1a;本专栏记录了从零学习单片机的过程&#xff0c;其中包括51单片机和STM32单片机两部分&#xff1b;建议先学习51单片机&#xff0c;其是STM32等高级单片机的基础&#xff1b;这样再学习STM32时才能融会贯通。 ☀️ 专栏适用人群 &#xff1a;适用于想要…

Ethercat学习-从站FOE固件更新(QT上位机)

文章目录 简介1、源码简介1、ec_FOEread2、ec_FOEwrite3、ec_FOEdefinehook 2、程序思路3、修改实现1、ecx_FOEwrite_gxf2、ecx_FOEread_gxf 4、其他5、结果6、源码连接 简介 FOE协议与下位机程序实现过程之前文章有提到&#xff0c;这里不做介绍了。这里主要介绍1、QT上位机通…

Python 中的循环向后迭代

文章目录 在 Python 中向后循环使用 reserved() 函数向后循环使用 range() 函数向后循环 总结 在 Python 中&#xff0c;数字迭代是通过使用循环技术来完成的。 在本文中&#xff0c;我们使用 Python 中的 reserved() 函数和 range() 函数执行循环。 在 Python 中向后循环 有许…

ChatGPT与网络安全

文章目录 一、“AI用于攻击”二、“AI用于安全&#xff08;防御&#xff09;”三、“AI的防御”四、“AI被攻击” ChatGPT作为基于生成式预训练模型&#xff08;GPT&#xff09;的聊天机器人&#xff0c;其核心技术是自然语言处理&#xff08;NLP&#xff09;。随着NLP技术的不…

剑指 Offer 14- I. 剪绳子解题思路

文章目录 题目解题思路优化 题目 给你一根长度为 n 的绳子&#xff0c;请把绳子剪成整数长度的 m 段&#xff08;m、n都是整数&#xff0c;n>1并且m>1&#xff09;&#xff0c;每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少&…

消息队列内容

问题有哪些&#xff1f; &#xff08;1&#xff09;消息队列为什么会出现&#xff1f; &#xff08;2&#xff09;消息队列能用来干什么&#xff1f; &#xff08;3&#xff09;使用消息队列存在的问题&#xff1f; &#xff08;4&#xff09;如何解决重复消费的问题&#…

Happy Equation(数论+讨论)

Happy Equation 题意 ​ 在 1 ≤ x ≤ 2 p 1\leq x\leq2^{p} 1≤x≤2p内&#xff0c;问有多少个x满足 a x ≡ x a ( m o d m ) a^{x}\equiv x^{a}\ (mod\ \ m) ax≡xa (mod m) 分析 ​ 通过打表发现&#xff0c;当a为奇数的时候&#xff0c;结果全为1&#xff0c;那么只需…