Protobuf如何提升编码效率

Posted vector6_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Protobuf如何提升编码效率相关的知识,希望对你有一定的参考价值。

Protobuf如何提升编码效率

Protobuf作为网络传输常用的编解码工具,面向多种平台,支持多种语言。使用 Protobuf 编码消息速度很快,消耗的 CPU 计算力也不多,而且编码后的字符流体积远远小于 JSON 等格式,能够大量节约带宽。那protobuf是如何提升编码效率的呢?具体的优化设计有以下几方面:

减少编码字段名使用的空间

通常消息由多个名、值对组成,对于字段名,Protobuf主要采用了映射表的方法。主要有两种:

  • 静态表。在HTTP请求中,对于多达几十字节的 HTTP 头部,HTTP/2 静态表仅用一个数字来表示,其中映射数字与字符串对应关系的表格,被写死在HTTP/2实现框架中。这样的编码效率非常高,但通用的 HTTP/2 框架只能将 61 个最常用的 HTTP 头部映射为数字,它能发挥出的作用很有限。

  • 动态表。由于静态表可映射的头部有限,动态表可以让更多的HTTP头部编码为数字。但动态表需要基于之前传输过的字段进行记录,即生效的前提是:必须在一个会话连接上反复传输完全相同的HTTP头部。如果消息字段在 1 个连接上只发送了 1 次,或者反复传输时字段总是略有变动,动态表就无能为力了。

那有没有办法既使用用静态表的预定义映射关系,又享受到动态表的灵活多变呢?**Protobuf把由HTTP/2 框架实现的字段名映射关系,在应用层自行完成了。**比如对于一个JSON消息,虽然一目了然,但但字段名 name、id、sex 其实都是多余的,因为客户端与服务器的处理代码都清楚字段的含义。

"name":"John","id":1234,"sex":"MALE"	

protobuf在 proto 文件中为每个字段预分配 1 个数字,编码时就省去了完整字段名占用的空

。例如对于上面的消息,Protobuf 将这 3 个字段名预分配了 3 个数字,定义在 proto 文件中:

message Person 
  string name = 1;
  uint32 id = 2;  

  enum SexType 
    MALE = 0;
    FEMALE = 1;
  
  SexType sex = 3;

接着,通过protoc程序便可以针对不同平台、编程语言,将它生成编解码类,最后通过类中自动生成的SerializeToString 方法将消息序列化,编码后的信息仅有11个字节。

Protobuf 可以用 1 到 5 个字节来表示一个字段名,因此,每个字节的第 1 个比特位保留,它为 0 时表示这是字段名的最后一个字节。因此,每个字节的第 1 个比特位保留,它为 0 时表示这是字段名的最后一个字节。下表列出了几种典型序号的编码值(请把黑色的二进制位,从右至左排列,比如 2049 应为 000100000000001,即 2048+1)。

综上,数字越小编码时用掉的空间也越小,实际网络中大量传输的是小数字,这带来了很高的空间利用率。Protobuf 的枚举类型也通过类似的原理,用数字代替字符串,可以节约许多空间。

高效编码字段值

Protobuf 对不同类型的值,采用 6 种不同的编码方式,如下表所示:

字符串用 Length-delimited 方式编码,顾名思义,在值长度后顺序添加 ASCII 字节码即

可。比如上文例子中的 John,对应的 ASCII 码如下表所示:

这样,"John"需要 5 个字节进行编码,如下图,绿色表示长度,紫色表示ASCII码:

字符串长度的编码逻辑与字段名相同,当长度小于 128(27)时,1 个字节就可以表示长度。若长度从 128 到 16384(214),则需要 2 个字节,以此类推。

由于字符串编码时未做压缩,所以并不会节约空间,但胜在速度快。如果你的消息中含有大量字符串,那么使用 Huffman 等算法压缩后再编码效果更好。

对于 id:1234 这个数字,Protobuf 中所有数字的编码规则是一致的,字节中第 1 个比特位仅用于指示由哪些字节编码 1 个数字。例如图中的 1234,将由14 个比特位 00010011010010 表示(1024+128+64+16+2,正好是 1234)。

由于消息中的大量数字都很小,这种编码方式可以带来很高的空间利用率。但是如果你确定数字很大,这种编码方式不但不能节约空间,而且会导致原先 4 个字节的大整数需要用5 个字节来表示时,你也可以使用 fixed32、fixed64 等类型定义数字。

除此之外, **Protobuf 定义了每个字段的默认值,因此,当消息使用字段的默认值时,Protobuf 编码时会略过该字段。**上图中sex:MALE 由于 MALE=0是sex的默认值,因此省去了2个字节

另外,当使用repeated 语法将多个数字组成列表时,还可以通过打包功能提升编码效率。**使用打包功能可以仅用 1 个字段前缀描述所有数值,它在列表较大时能带来可观的空间收益。**比如下图中,对 numbers 字段添加 101、102、103、104 这 4 个值后,如果不使用打包功能,共需要 8 个字节编码,其中每个数字前都需要添加字段名。而使用打包功能后,仅用 6 个字节就能完成编码,显然列表越庞大,节约的空间越多。

PS:在 Protobuf2 版本中,需要显式设置 [packed=True] 才能使用打包功能,而在Protobuf3 版本中这是默认功能。

以上是关于Protobuf如何提升编码效率的主要内容,如果未能解决你的问题,请参考以下文章

C++ protobuf使用介绍

gRPC笔记与相关问题

如何用“心流”提升编码工作效率?

教你使用ProtoBuf,通过gRPC服务在Android上进行网络请求

使用springboot cache + redis缓存时使用gzip压缩以提升性能

protobuf 3 定义的犄角旮旯