博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android开发——Protocol Buffer效率之高的原理介绍
阅读量:4047 次
发布时间:2019-05-24

本文共 3698 字,大约阅读时间需要 12 分钟。

0.前言

最近的项目里有用到Protocol BufferProtocol BufferGoogle公司开发的一种数据描述语言,类似于XML,是一种结构化数据的数据存储格式,可用于数据传输量较大的即时网络通信IM等场景。之所以使用它,是因为PB将信息序列化为二进制的格式,体积缩小了3倍,序列化速度比Json快了20-100倍,也必然会减少网络传输所需的时间。这么强大的的PB,当然要深入理解一下它为什么会如此高效。

1.信源编码和信道编码

信源编码:信源编码的目标就是使信源减少冗余,更加有效、经济地传输。

现代通信应用中常见的信源编码方式有:Huffman编码、算术编码、L-Z编码,这三种都是无损编码,另外还有一些采用压缩方式的有损编码。同时信源编码也根据,是否把一个传输单位编码为固定长度,区分为定长编码变长编码

 

信道编码:信道编码是为了对抗信道中的噪音和衰减,通过增加冗余来提高抗干扰能力以及纠错能力。信道编码的本质是降低误码率、增加通信的可靠性。

数字信号在传输中往往由于各种原因,使得在传送的数据流中产生误码,所以通过信道编码这一环节来避免码流传输中误码的发生。常用的处理技术有奇偶校验码、纠错码、信道交织编码等。

 

通过上面的简单介绍,我们首先给PB一个初步的定义,从通信角度来看,PB就是一种变长的无损的信源编码。接下来主要介绍PB在编码角度的性能优势。

2.PB对整数的编码优化

先考虑最简单的情况,PB如何对一个整数值进行编码。

一般情况下我们是将一个int值看作4字节,也就是所谓的定长编码PB考虑到现实情况中,数值较大的数比数值较小的数更少地被使用这一事实,采用了变长编码,即如果一个数能够用1个字节来表示,那就用一个字节来表示。如数值1就会被编码为0000 0001,而不是把它编码为0000 0000 0000 0000 0000 0000 0000 0001。但是也由此产生一个问题,每个整数的编码长度可能不一样,如何区分边界呢

 

PB将每个字节拿出最高位的1比特来作为边界的标记。如果这个标记等于1,则表示还没有到最后一个字节,否则表示到了最后一个字节。虽然造成了比特的1/8的浪费,一个很大的数将可能使用5个字节来表示。但是由于数值较小的数通常可以用12个字节来表示,和定长的4个字节编码相比,仍然有很大的优化。

边界问题解决之后,内容部分填入整数的补码即可。举例:

//0000 0001表示整数1//1010 1100 0000 0010将两字节的最高位去掉后是 010 1100 000 0010//PB采用低字节优先,即后面的字节要放在高位,于是拼在一起的结果为0000010 0101100//最终表示的是300这个整数值

 

3.  PB对于Key-Value对中key的优化

对于一个对象,里面包含多个变量,如何编码呢?比如一个类的定义为:

class Test{    String studentName;    String studentId;    String studentSchool;    int age;}

如何使用Json,那么传输的格式可能如下:

{"studentName":"SEU_Calvin","studentId":"150884","studentSchool":"东南大学","age":"18"}
PB认为"studentName"、"studentId"、"studentSchool"等这些
变量名不应该包含在传输消息中,因为编解码、传输这些信息也需要资源。PB为了
节省空间,在通信双方都保持一份文档,记录了"studentName"、"studentId"的编号,比如上述四个变量名字分别编号为1、2、3、4。于是在序列化的时候,只需要传输下面的信息:

1:"SEU_Calvin",2:"150884",3:"东南大学",4:"18"

对方本身保留了一份编号文档,于是就可以反序列化了。这些编号本身也可以用上面对整数的编码优化方式进行编码。 

这里有个问题,如果接受端直接按照某种约定顺序解码,发送端就不需要传1234这些编号了,可以吗?其实不可以的,因为如果存在某些变量值缺省了,就必须在相应字段填入默认的缺省值,这样反而浪费空间。或者加入变量之间的间隔符,显然也是不合理的。

 

4.  PB对于Key-Value对中Value的优化

4.1  Value为整数

如果Value是整数,那么直接使用前面提到的2中对整数的编码优化即可。即大多数整数只占一两个字节。

4.2  Value为字符串

但是如果Value是一个很长的字符串,每个字节都拿出1个比特来区分边界就太浪费空间了,而且字符串本身就是一个一个字节的,被打乱后也会影响解码效率。因此,PBValue长度信息的指示可以放在KeyValue之间。(长度本身也是一个整数,就用前面那种方法进行编码即可),在解码Value时,解析长度就可以知道Value值到哪里结束。

 

但是也因此产生一个问题,比如4.1中整数这种情况,Value中已经自带了结束标识符,那我们就不需要Value的长度指示信息了呀。因此PB引入了Type类型,即提前告诉接收端Value的类型PB将这个Type信息放在了Key中的最后3个比特中。根据这个Type,即可让接受端注意或者忽略Value的长度字段。Value的类型在PB中称为wire_type。主要有以下几种:

//wire type=0 //表示这个Value是一个变长整数,比如int32, int64, uint32, uint64, sint32, sint64, bool, enum//wire type=1//表示这个Value是一个64位的定长数,比如fixed64, sfixed64, double//wire type=2//表示string, bytes等,这些Value的长度需要置于Key后面//wire type=3//表示groups中的Start Group,就是有一组,3表示接下来的Value是第一组//wire type=4//表示groups中的End Group//wire type=5//表示32位固定长度的fixed32, sfixed32, float等

5.  举例介绍

上面讲了那么多,我们用两个例子来说明一下。

 

1

比如08 ac 02这三个字节。

08二进制为0000 1000,去除最高位为 0001 000

最后3个比特为Type类型,000表示wire type=0,前面的0001表示这是编号为1的变量。

后面的ac02,写成二进制为10101100 00000010,去掉最高位分隔符为0101100 0000010,因为低字节优先,于是串起来为0000010 0101100 = 300

最终,08ac 02这三个字节解码为编号为1的变量值为整数300

 

2

比如12 07 74 65 73 74 69 6e 67这几个字节。

12的二进制为0001 0010,最高位0表示这是最后一个字节,去除最高位后是0010  010,最后3个比特010表示wire type=2,前四位0010表示这是编号为2的变量。

因为wire type=2,表示ValueStringbytes等变长流。接下来要解码Value的长度。

07的二进制为0000 0111,最高位为0,表示这是最后一个字节,去除最高位后是000 01117),表示Value的长度为7,也就是后面的7个字节:74 65 73 74 69 6e 67

7个字节如果是String,那么根据ASCII码可解码为“testing”。

最终,1207 74 65 73 74 69 6e 67这几个字节解码为编号为2的变量值为字符串“testing

 

6.  总结PB的体积压缩优势

根据5中的例子,08 ac 02 12 07 74 65 73 74 69 6e 67这几个字节即可等价于Json中的

{"testInt":"300","testString":"testing"}

可以看出,Json使用了40个左右的字节,而PB只使用了12个字节,这也就解释了为什么PB将信息序列化为二进制后,体积缩小了3倍,也因此减少了数据网络传输的时间

 

7.  总结PB的序列化速度优势

至于
为什么序列化速度提高了
20-100
,这里简单提一下,以
XML
的解包过程为例,
XML
首先需要将得到的字符串转换为
XML
文档对象的结构模型,之后再从结构模型中读取指定节点的字符串,最后再将这个字符串指定为某个对象的变量值。这个过程非常复杂,其中转换文档对象结构模型的过程,通常需要完成词法文法分析等大量消耗
CPU
的复杂计算。而
PB
只需要
简单地将一个二进制序列,按照指定的格式
读取赋值到某个对象的变量值即可。因此
Json
的序列化速度非常快。

转载地址:http://iuzci.baihongyu.com/

你可能感兴趣的文章
MongoDB 数据文件备份与恢复
查看>>
数据库索引介绍及使用
查看>>
MongoDB数据库插入、更新和删除操作详解
查看>>
MongoDB文档(Document)全局唯一ID的设计思路
查看>>
mongoDB简介
查看>>
Redis持久化存储(AOF与RDB两种模式)
查看>>
memcached工作原理与优化建议
查看>>
Redis与Memcached的区别
查看>>
redis sharding方案
查看>>
程序员最核心的竞争力是什么?
查看>>
Node.js机制及原理理解初步
查看>>
linux CPU个数查看
查看>>
分布式应用开发相关的面试题收集
查看>>
简单理解Socket及TCP/IP、Http、Socket的区别
查看>>
利用HTTP Cache来优化网站
查看>>
利用负载均衡优化和加速HTTP应用
查看>>
消息队列设计精要
查看>>
分布式缓存负载均衡负载均衡的缓存处理:虚拟节点对一致性hash的改进
查看>>
分布式存储系统设计(1)—— 系统架构
查看>>
MySQL数据库的高可用方案总结
查看>>