今天讨论面试情况的时候,同事说问了一个候选人protoBuf的问题,为什么protocolbuffer更节省空间,更省流量,更有效率。我有一点印象,但也不多了。正好查看一下再做一下系统对比。
先说一下,为什么我们要思考这个问题,实际上还是优化相关的。我们会关注流量,关注内存,关注序列化,关注加密,因此在数据传输/存储的过程中,我们会选择市面上已有的协议也进行对比。
我们先说下哪两个方面经常会考虑这个问题。
第一:数据表和序列化。因为数据表通常也是一个数据结构,序列化也经常是对数据对象的序列化,而不论是proto还是msgPack这些,都是把数据结构打包成二进制存储在本地,本身就是符合定义的。
第二:数据包。网络通信通常都会选择一种格式的,在测试阶段通常会用json等比较简单的,不过商业化项目一般从各方面考虑都会至少用二进制,至于用不用一些开源的协议见仁见智。
但数据表和序列化,同数据包又有一些细微的关注点不同。因为数据表往往是针对整张表进行序列化然后存储在一个二进制文件里的,而proto实际上不关心这些,一条协议里,也通常只发送一个对象的二进制流。所以对于本地存储而言,实际上会多涉及一个打包问题,把哪些数据表、哪些行,打到一个文件里去,读取的时候是否需要整个文件都读进内存?
我们不会讨论问题的全部,还是关注与protobuf所关联的问题吧。
那第一个,就是如何节省空间呢?核心还是变长整形。
这个资料还是大家自己查,比较传统的一个压缩算法。而且效率不算低,能用的建议都用上。我这里对比两个问题:
1、负数的处理
第一种是,负数直接按照二进制处理,转换为uint(算法比较好写),直接按照Virant规则去写,由于负数的最高位为1,所以基本上处理起来,是重要用5个字节去保存。因为处理后正数的范围大概在0~21亿之间,负数的范围在21~42亿之间
ProtoBuf:采用zigzag的方式处理处理负数,这样的好处是,处理后的结果,正数按照大小全部分布在偶数上,负数全部分布在奇数上,这样好处是,对于比较小的正数和负数,压缩后的二进制都比较小。但缺点是,正数和负数转换后是交替存在的,所以叫ZigZag
2、小数的处理
个人做法是,把小数乘以10000转换为整数然后存储,读取的时候除以10000,缺点是丢失精度(超过小数点4位数,而且过大的小数无法保存),优点是简单,而且内存节省大部分情况还是可以的。
Proto:这个我没有看太清,根据资料,貌似Proto处理是直接把小数内存转为整形的二进制,当做整形去处理,具体能压缩多少,取决于编码格式。有知道的可以留言告诉我一下。是不是这样。在C++里,这种操作还是相对容易的,通过指针转换,*但在C#里,是需要一定技巧的。
至于压缩的处理,我没有太关注,对于proto来说,没有分段读取和按需读取的需求,所以性能问题取决于压缩算法本身。这里不再讨论,如果对于本地存储的数据表之类的话,倒是关注一点,就是如果是基础Chrunk的压缩,还是可以基于Chrunk的读取,然后反序列化,这样对内存是友好的。如果是基于重复字符串那种压缩算法,基本上还是要把整个文件内容读取到内存里再进行反序列化,所以就看最终,你对外存还有内存哪个更关心了。
翻译
搜索
复制