当前位置:首页 > 数码 > b-b-Protobuf编码原理及优化技巧的全面讨论 (BBP溶剂)

b-b-Protobuf编码原理及优化技巧的全面讨论 (BBP溶剂)

admin4个月前 (05-06)数码16

作者:carmark

1、Protobuf编码原理引见

序列化算法被宽泛运行于各种通讯协定中,本文对序列化算法启动狭义定义:

将某个struct或class的内存数据和通讯数据链路上的字节流启动相互转化的算法。

基于这个定义序列化算法具备两个行为:

1、 序列化 :内存数据->通讯链路字节流

2、 反序列化 :通讯链路字节流->内存数据

罕用的序列化算法有:json、xml、protobuf等,将这些算法启动演绎不难发现这些算法关键是对三种基本类型(原子性、无法被拆分)和三种复合类型(由基本类型和其余合乎类型造成)启动序列化和反序列化。

1、 基本类型 :定点数值类型、浮点数值类型、字符串类型

2、 复合类型 :结构体类型、数组类型、map类型

protobuf也是对这些类型启动序列化的,下文将在proto3语法的背景下引见protobuf对不同类型的编码原理。

1.1基本类型

1.1.1定点数值类型

proto3语法中:int32、int64、uint32、uint64、sint32、sint64、fixed32、fixed64、sfixed32、sfixed64、bool、enum属于定点数值类型。关于 int32、int64、uint32、uint64 会间接经常使用varint编码,类型会间接经常使用一个字节存储,可以看成是一个int32类型。关于 sint32、sint64 类型会先启动zigzag编码,再启动varint编码,关于 fixed32、fixed64、sfixed32、sfixed64 类型会经常使用定长的四个或八个字节启动存储。

关于varint编码和zigzag编码的细节可以参考文档,本文间接给出两种编码的性质:

varint编码 :变长编码,关于小正整数有较好的紧缩成果,关于大整数或正数编码后字节流长度会变大。

zigzag编码 :定长编码,将小正整数和小负整数转换到小正整数再启动varint编码,对相对值较小的整数有良好的紧缩成果。

1.1.2浮点数值类型

proto3语法中:float和double属于浮点数据类型,经常使用定长的四个字节或八个字节存储,数据间接用规范示意。

1.1.3字符串类型

proto3语法中:string、bytes属于字符串类型,字符串类型序列化后的字节流为其 原始内容自身 。这两种类型的不同之处在于string内的字节流必定是utf8编码,bytes没有这种要求。

1.2复合类型

1.2.1结构体类型

proto3语法中经常使用message定义结构体类型,结构体类型有多个不同tagid造成的字段,字段可以是基本类型或复合类型,甚至可以是这个结构体类型自身。结构体每个字段底层都经常使用这种格局启动存储, 须要留意的是typeid、length、data三局部长度会依据实践状况出现扭转。

typeidlength>typeid用于存储结构体字段编号(tagNum)和字段类型(tagType),tagNum为字段=后的数字,tagNum也经常使用varint启动编码,因此假设=后的数字很大,则或者造成tagNum编码变大,tagid占用多个字节。而tagType则指明数据类型,这局部固定占用三个bit。
|tagNum|tagType|+----------------------+|xxxxxxxx|+----------------------+720

下表记载了不同字段类型对应的tagType值:

tagType类型0int32、int64、uint32、uint64、sint32、sint64、bool、enum1fixed64、sfixed64、double2string、bytes、结构体类型、数组类型、map类型3弃用4弃用5fixed32、sfixed32、float

length局部示意data局部的长度,雷同经常使用变长varint编码,须要留意的是假设字段类型是数值类型,则length局部不会出序列化后的字节流中。

data局部为原始数据,可以是基本类型和复合类型序列化后的字节流,算法通常递归的对这些字段启动处置。

1.2.1数组类型

proto3语法中经常使用repeated为前缀的字段即为数组类型,也就是说repeated关键字是用来润色结构体类型的字段的。

假设repeated润色的是定点数值类型或浮点数值类型,在proto3语法下会自动依照下图方式将这些数值陈列在一同,length局部记载data1~dataN一切数值的字节数之和。

typeidlength>假设润色的是其余类型则会依照以下方式组织这些数据(其中field1为数组类型),须要留意的是属于同一个数组的不同元素两边或者有其余字段的元素拔出。
typeid1length1>1.2.2map类型

proto3语法中map也是一种润色符,润色结构体类型的字段。map类型的key必定为定点数值类型或string类型,map的底层存储key-value键值对,驳回和数组类型一样的存储方法,数组中每个元素是kv键值对。以下数据定义中,messageA和messageB有齐全相反的底层存储结构。

messageA{map<int32,float>mp=1;}messageKV{int32K=1;floatV=2;}messageB{repeatedKVmp=1;}

1.3类型自动值

假设类型为自动值,则该字段tagid+length+data不会出如今序列化后的字节流中。

类型自动值int32、int64、uint32、uint64、fixed32、fixed64、sfixed32、sfixed64、float、double0enum0对应的枚举值boolfalsestring""bytes空字节流结构体类型null(对应字段的指针为空)数组类型空数组map类型空map

须要留意的是假设某个字段是结构体类型,该字段对应的结构体中的一切元素均为自动值,这种状况下该字段的data局部会被省略,只保管tagid和length局部,当然length局部值为0。假设字段的指针为空,则该字段不会有任何内容出如今序列化后的字节流中。

1.4举例

enumC{C1=0;C2=1;}messageB{int32X=1;sint32Y=2;CZ=3;}messageA{repeatedfloatF1=1;map<string,B>F2=20;}messageA内存中的数值:F1:1.2F1:2.3F2:{key:"123"value:{X:1Y:-1Z:C2}}messageA序列化后的字节流:0XA,0X8,0X9A,0X99,0X99,0X3F,0X33,0X33,0X13,0X40,0XA2,0X1,0XD,0XA,0X3,0X31,0X32,0X33,0X12,0X6,0X8,0X1,0X10,0X1,0X18,0X1|---------------------A.F1---------------------|-------------------------------A.F2----------------------------------||-------1.2---------|---------2.3------|A.F2.tagid|+----------|----"123"----|-------------|1|------|-1|----|C2|

须要留意的是messageA的F2字段的tagNum是20,而tagType值是2,依照上文讨论的编码原理A.F2.tagid编码如下:

+----------------------++----------------------+|00000001||10100010|+----------------------++----------------------+1520|typeNum|tagType||varint编码,编码前:00010100,值为20|值为2|

从这个例子还可以看出,protobuf序列化之后低地址字节在前,洼地址字节在后。

Q:protobuf既然有了int32为什么还要用sint32和fixed32?

A:int32经常使用varint编码,关于小正数有较好的紧缩成果,关于大整数和正数会造成额外的字节开支。因此引入fixed32,该类型不会对数值启动任何编码,对大于2^28-1的整数比int32占用更少的字节。而关于正数经常使用zigzag编码,这样相对值较小的正数都能被有效紧缩。

Q:为什么数组类型每个元素都要用tagid+length+data这种格局启动存储?

A:其实我也感觉这点设计不太正当,为什么不设计成tagid+元素个数+{length+data}...这种格局呢?

Q:map类型是不是尽量别用?

A:假设你的业务对序列化后的字节流长度有要求,能不用就别用吧。

Q:为什么数组类型和map类型的元素两边或者拔出其余字节流?

A:不分明,不过这倒是解释了第二个疑问。

Q:既然通讯双方都经常使用.proto文件商定了字段的类型,为什么tagid字段还要蕴含type消息?

A:不分明,不过或者和不同版本协定的兼容性无关。

2、优化技巧讨论

经过剖析protobuf的编码原理,可以发现假设对序列化后的字节流长度有要求,无脑地定义数据结构是很不明智的,本节将讨论局部优化技巧。

2.1类型优化

上文中屡次提到过varint编码和zigzag编码,不同的数据类型经常使用不同的编码方法,那应该如何选用呢?首先给出正整数经过varint编码后占用字节数的算式,其中x示意待计算的正整数,y示意占用字节数。

关于zigzag编码须要明白一点:zigzag编码须要和varint编码一同经常使用。zigzag编码可以看作将正负交替的数值序列映射至正整数序列,之后再由varint对正整数启动编码。

原始数值:0,-1,1,-2,2,-3,3,-4,4...

映射数值:0,1,2,3,4,5,6,7,8...

如今思考经常使用varint编码后4字节能示意的最大无符号整数,依据算式:令y=4,易得x最大值为2^28^-1。因此可以获取论断,关于小于2^28^-1的无符号整数介绍经常使用varint编码,关于大于2^28^-1的无符号整数经常使用varint编码会造成编码后字节数变长。

而后讨论经常使用zigzag+varint编码后4字节能示意的正正数范畴,联合以上剖析不难得出4字节能示意的正正数范畴是[-2^14^,2^14^-1]。因此关于在此范畴内的数值,经过zigzag+varint编号后的字节流长度小于四个字节。

基于以上剖析下表给出不同数值范畴的定点数值介绍类型(以下介绍类型都是基于最小字节流长度为指标,编解码环节会存在必定cpu消耗)

数据范畴介绍类型[-2^63^,-2^31^)sfixed64[-2^31^,-2^28^)sfixed32[-2^28^,-2^14^)sint64[-2^14^,2^14^-1]sint32(2^14^-1,2^28^-1]uint32或int32(2^28^-1,2^32^-1]fixed32(2^32^-1,2^56^-1]uint64或int64(2^56^-1,2^64^-1]fixed64

2.2结构优化

从对protobuf编码原理的引见那局部可以看出,protobuf由于思考兼容性要素,存储了很多tagid、length这些记载结构消息的字段。在实践运行场景中假设数据的结构较为严密(这个词临时还无较为准确的定义),多个字段都有相反的结构能否能去掉记载结构消息的字段,只保管内容消息的字段,从而缩小数据长度呢?本文提供一种优化思绪。

优化前:

messageA{int32x=1;int32y=2;}messageB{int32z=1;}messageC{repeatedAas=1;Bb=2;}messageC序列化后字节流:0XA,0X4,0X8,0X1,0X10,0X2,0XA,0X4,0X8,0X1,0X10,0X2,0XA,0X4,0X8,0X1,0X10,0X2,0X12,0X2,0X8,0X3messageC内存中数值:as:{x:1y:2}as:{x:1y:2}as:{x:1y:2}b:{z:3}

优化后:

messageC{repeatedint32xs=1;repeatedint32ys=2;int32z=3;}messageC序列化后字节流:0XA,0X3,0X1,0X1,0X1,0X12,0X3,0X2,0X2,0X2,0X18,0X3messageC内存中数值:xs:1xs:1xs:1ys:2ys:2ys:2z:3

前后对比可看在传输相反的消息状况下字节流长度减半,这关键由于舍弃了很多tagID字段。当然这种优化思绪是基于数据的结构较为严密这一假定:优化前大局部messageA中的X、Y字段均非自动值,这样就可以省略少量结构消息,从而缩小字节流长度。实践运行中可以少量运行这种技巧,来优化编解码性能。

2.3数据优化

除了却构消息能否有其余消息可以被省略掉呢?当然,除了却构消息还有数据消息,即为data字段记载的值。依据消息论的基本观念,假设一组数据散布范畴很广,每个数据点现频率简直相反则须要用较多bit对这组数据启动编码。假设这组数据散布较为繁多,某些数据点出现频率较高,这种数据散布能较好地经常使用变长编码示意。

推行到实践业务场景中,假设发现某组数据的某些字段满足某些散布特色,比如:期间戳、买卖ID,这种散布范畴较小,重复性较高的数据,最便捷的方法是:经常使用一个int64存储这组数据的最小值,而后关于这组数据中的其余元素区分计算和这个最小值的差值。由于数据散布范畴较小,因此差值也不会很大,从而缩小全体编码长度。

优化前:

messageA{repeatedint64timestamps=1;}messageA序列化后字节流:0XA,0X1E,0XCA,0XDE,0XA5,0XAF,0XAD,0X31,0XCE,0XDE,0XA5,0XAF,0XAD,0X31,0XD2,0XDE,0XA5,0XAF,0XAD,0X31,0XD6,0XDE,0XA5,0XAF,0XAD,0X31,0XDA,0XDE,0XA5,0XAF,0XAD,0X31,messageA内存中数值:timestamps:1695805960010timestamps:1695805960014timestamps:1695805960018timestamps:1695805960022timestamps:1695805960026

优化后:

ProtobufmessageA{int64base=1;repeatedint64timestamps=2;}messageA序列化后字节流:0X8,0XCA,0XDE,0XA5,0XAF,0XAD,0X31,0X12,0X5,0X0,0X4,0X8,0XC,0X10,messageA内存中数值:base:1695805960010timestamps:0timestamps:4timestamps:8timestamps:12timestamps:16

由于差值通常较小,在此基础上可以继续启动bit优化,比如最大差值是15则最多用4个bit示意一个差值即可,这样一个字节就可以记载两个差值的消息,从而进一步紧缩序列化字节流。

3、未来上班展望

基于上述剖析和通常,不难发现protobuf启动序列化的环节中,须要贮存结构消息数据消息。关于结构严密的数据,protobuf会造成少量bit用于表征结构消息。而假设数据消息中存在某些先验数据散布或法令,protobuf也会存储较多冗余消息。那么能否有算法能较好地联合待序列化数据的个性,进而防止消息冗余呢?这个或者须要进一步的钻研...


H264之帧编码——透析(I帧+P帧+B帧编码)原理与流程

在H.264压缩标准中I帧、P帧、B帧⽤于表⽰传输的视频画⾯。在视频压缩中,每帧都代表着一幅静止的图像。在实际的视频压缩编码时,会采取各种算法减少数据的容量,其中IPB帧就是最常见的一种算法。

I‑frame (Intra-coded picture): 即完整的一张图片

P‑frame (Predicted picture): 与前面一张图片的区别的区域

B‑frame (Bidirectional predicted picture):与前面以及后面的图片的区别区域

I帧⼜称帧内编码帧,又称全帧压缩编码帧,是⼀种⾃带全部信息的独⽴帧,⽆需参考其他图像便可独⽴进⾏解码,可以简单理解为⼀张静态画⾯。视频序列中的第⼀个帧始终都是I帧,因为它是关键帧。I帧通常是每个GOP(MPEG所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,作为随机访问的参考点,可以当成静态图像。

现在有一段影片如下:

该影片总共是20张图片组成的,每一张完整的图片我们都可以叫做I帧。假如每张JPEG的图片大小为100KB,那么传输两张图片即为100 * 20 = 2000KB

要知道这个还不到几秒的影片就2M了,要是几个小时的那还得了,所以就有了影片压缩的算法

1.它是一个全帧压缩编码帧。它将全帧图像信息进行JPEG压缩编码及传输; 2.解码时仅用I帧的数据就可重构完整图像; 3.I帧描述了图像背景和运动主体的详情; 4.I帧不需要参考其他画面而生成; 5.I帧是P帧和B帧的参考帧(其质量直接影响到同组中以后各帧的质量); 6.I帧是帧组GOP的基础帧(第一帧),在一组中只有一个I帧; 7.I帧不需要考虑运动矢量; 8.I帧所占数据的信息量比较大。

P帧⼜称帧间预测编码帧,又称前向预测编码帧,需要参考前⾯的I帧才能进⾏编码。表⽰的是当前帧画⾯与前⼀帧(前⼀帧可能是I帧也可能是P帧)的差别。解码时需要⽤之前缓存的画⾯叠加上本帧定义的差别,⽣成最终画⾯。

与I帧相⽐,P帧通常占⽤更少的数据位,但不⾜是,由于P帧对前⾯的P和I参考帧有着复杂的依耐性,因此对传输错误⾮常敏感。通常将图像序列中前面已经编码帧的时间冗余信息充分去除来压缩传输数据量的编码图像,也称为预测帧。通过观察,我们可以看出,实际上每一帧之间其实只有一部分细微的差别而已,如下图提取了6帧

当传输完第一帧以后,第二帧其实我们只需要传输一部分,然后由另外一端进行图片算法来进行组合

用这种方式,在传输第二帧的时候,还不到原来的1/10,只需要传输第一帧的100KB,后续的都是按照这种方式传输部分,这种只传输部分的图片,就是P帧了。最后整个影片的大小即为100KB + 10KB * 19 = 290 KB,比原来缩小了很多。

1.P帧是I帧后面相隔1~2帧的编码帧; 2.P帧采用运动补偿的方法传送它与前面的I或P帧的差值及运动矢量(预测误差); 3.解码时必须将I帧中的预测值与预测误差求和后才能重构完整的P帧图像; 4.P帧属于前向预测的帧间编码。它只参考前面最靠近它的I帧或P帧; 5.P帧可以是其后面P帧的参考帧,也可以是其前后的B帧的参考帧; 6.由于P帧是参考帧,它可能造成解码错误的扩散; 7.由于是差值传送,P帧的压缩比较高。

B帧⼜称双向预测编码帧,又称双向预测内插编码帧,也就是B帧记录的是本帧与前后帧的差别。也就是说要解码B帧,不仅要取得之前的缓存画⾯,还要解码之后的画⾯,通过前后画⾯的与本帧数据的叠加取得最终的画⾯。B帧压缩率⾼,但是对解码性能要求较⾼。既考虑源图像序列前面的已编码帧,又顾及源图像序列后面的已编码帧之间的时间冗余信息,来压缩传输数据量的编码图像,也称为双向预测帧。B帧其实就是与前后两张图片的区别。如果理解了P帧和I帧,这个就很好理解了。

B帧比P帧更小,更节省空间

假设现在有三张图片,如下图:

在经过编码后,会变成如下:

1.B帧是由前面的I或P帧和后面的P帧来进行预测的; 2.B帧传送的是它与前面的I或P帧和后面的P帧之间的预测误差及运动矢量; 3.B帧是双向预测编码帧; 4.B帧压缩比最高,因为它只反映丙参考帧间运动主体的变化情况,预测比较准确; 5.B帧不是参考帧,不会造成解码错误的扩散。

(1) 进行帧内预测,决定所采用的帧内预测模式。 (2) 像素值减去预测值,得到残差。 (3) 对残差进行变换和量化。 (4) 变长编码和算术编码。 (5) 重构图像并滤波,得到的图像作为其它帧的参考帧。

(1) 进行运动估计,计算采用帧间编码模式的率失真函数(节)值。P帧只参考前面的帧,B 帧可参考后面的帧。 (2) 进行帧内预测,选取率失真函数值最小的帧内模式与帧间模式比较,确定采用哪种编码模式。 (3) 计算实际值和预测值的差值。 (4) 对残差进行变换和量化。 (5) 熵编码,如果是帧间编码模式,编码运动矢量

Android音视频工程师必备《全套音视频入门到精通手册》

I帧只需考虑本帧;P帧记录的是与前⼀帧的差别;B帧记录的是前⼀帧及后⼀帧的差别,能节约更多的空间,视频⽂件⼩了,但相对来说解码的时候就⽐较⿇烦。因为在解码时,不仅要⽤之前缓存的画⾯,⽽且要知道下⼀个I或者P的画⾯,对于不⽀持B帧解码的播放器容易卡顿。

影响沟通编码的因素不包括()。a技巧b态度c经济背景d知识

影响沟通编码的因素不包括经济背景。

牙买加出生的英国学者斯图亚特∙迈克菲尔∙霍尔(Stuart McPhail Hall)1973年提出来“编码/解码沟通模式(encoding/decodin model of communication)。这个理论为当时的电视媒体如何制作,传播信息,这些信息又是被如何解读的提供分析方法。

编码指的是传递信息方把要传达的含义(meaning)制作出来;解码指的是接受信息方把含义释放出来。

很明确的,”码“是一套规定的把含义和符号对应起来的规则体系,比如语言就是一种码;以前用电报发信息时要用到电报码。如果双方用的是不同的码,可以想象结果是什么。鸡同鸭讲,就是鸡用鸡的码,鸭用鸭的码,谁也不明白谁。

沟通双方用的码越明确,越精准,沟通的效率就越高。比如电脑所用以编程的各种语言,代码都是标准化的,计算机可以准确解读执行。

相反,人类语言虽然是一种码,是传递思想的工具,却是很”松散“的码,虽然有语法规范使用,但在最基本的语言单位“字”和“词”上,都包含着不同的意思。

且不说汉语这类高语境的语言,一字根据上下文多义是常态,就算英文这种低语境的语言,一个词也一样有不同的解读。比如“longterm”(长期)这个词,多久算长,多久算短,每个人放在里面的含义都不一样,两年?十年?二十年?如果问一个小孩子,长期可能就只有一个月!

免责声明:本文转载或采集自网络,版权归原作者所有。本网站刊发此文旨在传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及版权、内容等问题,请联系本网,我们将在第一时间删除。同时,本网站不对所刊发内容的准确性、真实性、完整性、及时性、原创性等进行保证,请读者仅作参考,并请自行核实相关内容。对于因使用或依赖本文内容所产生的任何直接或间接损失,本网站不承担任何责任。

标签: Protobuf