为什么专门开一个坑,来使用pb。放弃本在各平台上都支持得很好的json而使用pb的一个归根到底的理由,就是希望在保证强类型和跨平台的情况下,能够更轻,更快,更简单。既然是奔着这个目标去的,到底多快我需要一个合理的解释。
在使用pure python官方库的的情况下,对比了pb和json标准库,还有simplejson库的速度。
使用的.proto文件文件如下:
syntax = "proto2";package hello_word;message SayHi { required int32 id = 1; required string something = 2; optional string extra_info = 3;}
python文件可以根据这个生成对应的SayHi obejct。
测试各库序列化速度的代码如下所示:
# coding: utf-8import timeit# 序列化x = """say_hi.SerializeToString()"""y = """json.dumps(ppa)"""z = """simplejson.dumps(pl)"""print min(timeit.repeat(stmt=x, setup="import say_hi_pb2;" "say_hi = say_hi_pb2.SayHi();" "say_hi.id = 13423;" "say_hi.something = 'axiba';" "say_hi.extra_info = 'xiba';", repeat=5, number=100000))print min(timeit.repeat(stmt=y, setup="import json; " "ppa={" "'id': 13423," "'something': 'axiba'," "'extra_info': 'xiba'," "};", repeat=5, number=100000))print min(timeit.repeat(stmt=z, setup="import simplejson; " "pl={" "'id': 13423," "'something': 'axiba'," "'extra_info': 'xiba'," "};", repeat=5, number=100000)) 输出:
1.08438277245
0.3988001346590.707333087921测试各库反序列化速度的代码如下所示:
# coding: utf-8import timeit# 反序列化x = """say_hi.ParseFromString(p)"""y = """json.loads(p1)"""z = """simplejson.loads(p2)"""print min(timeit.repeat(stmt=x, setup="import say_hi_pb2;" "say_hi = say_hi_pb2.SayHi();" "say_hi.id = 13423;" "say_hi.something = 'axiba';" "say_hi.extra_info = 'xiba';" "p = say_hi.SerializeToString()", repeat=5, number=100000))print min(timeit.repeat(stmt=y, setup="import json; " "ppa={" "'id': 13423," "'something': 'axiba'," "'extra_info': 'xiba'," "};" "p1 = json.dumps(ppa)", repeat=5, number=100000))print min(timeit.repeat(stmt=z, setup="import simplejson; " "pl={" "'id': 13423," "'something': 'axiba'," "'extra_info': 'xiba'," "};" "p2 = simplejson.dumps(pl)", repeat=5, number=100000)) 输出:
0.924090862274
0.4926319122310.283575057983从上面的数据可以看出,在我使用的版本3.1.0.post1的情况下,纯python实现pb序列化的速度略慢于json原生库两倍多,比simplejson库慢百分之30。在反序列化的速度测试中,依然是pb速度最慢两倍慢于原生json库,慢于simplejson库3倍多。这样看起来差距似乎被优化得不那么大了。记得以前在使用pb2.x库的时候,python序列化常慢于simplejson 3倍以上是非常正常的事情。各分析性能的文章都可以看到 too slow这个描述。由于二进制存储,以及pb独特的编码二进制的方式,从大小的角度来说,pb远远小于json,但是速度连json都快不过,我们有什么理由放弃使用方便可依赖的json转而使用pb呢?这的确没有什么说服力。
然而,pb官方提供了一个c++实现 runtime for python,按照实践一中的方法,安装好最新的pb库,并且按照文档编译好,然后安装python 的c++实现,就可以让pb使用c++实现进行序列化反序列。其他生成代码之类的所有不用变,调用代码也不用变,只需要安装好就可以了。安装好之后可以看到
Using /Users/piperck/Desktop/grpc/lib/python2.7/site-packages
Finished processing dependencies for protobuf==3.1.0
再次使用pip list查看我们的pb的时候可以发现,已经被该库替代。
让我们来重新运行一下 序列化和反序列化的代码:
序列化输出:0.0857851505280.4031720161440.755691051483反序列化输出:0.0902311801910.4997339248660.297739028931
可以看到几乎比pure python的实现快近10倍。如果把序列化和反序列按照一次计算进行计算的话,也比我们通常使用的simplejson库快上4到5倍。再频繁调用序列化反序列化的应用中,可以说还是比较大的性能提升了,可以使得你的代码更轻更快,而且强类型映射可以检查错误。
别以为到这里就完了。还有一个更快速的库,但是现在只支持proto2,叫Pyrobuf Library。基于cPython实现,根据作者的说法,他要比c++ backend for python 还要快上2-4倍。让我们来尝试一下。
首先安装一下:
pip install pyrobuf
如果不行可以尝试使用:
pip install pyrobuf -v -v -v --upgrade --force --no-cache
安装好之后,按照官网的提示,使用pyrobuf 的 cli命令行界面,对.proto文件进行编译,得到.pxd和.pyx文件,还有.o和.c还有.so的文件(注意他们需要在同一个文件夹下)。
一切完成之后书写代码 测试速度:
import timeito = """p.SerializeToString()"""print min(timeit.repeat(stmt=o, setup= "from hello_world_say_hi_proto import SayHi;" "p = SayHi();" "p.id = 3;" "p.something = 'axiba';" "p.extra_info = 'xiba'", repeat=5, number=100000))o = """p.ParseFromString(oi)"""print min(timeit.repeat(stmt=o, setup= "from hello_world_say_hi_proto import SayHi;" "p = SayHi();" "p.id = 3;" "p.something = 'axiba';" "p.extra_info = 'xiba';" "oi = p.SerializeToString()", repeat=5, number=100000)) 输出:
0.069412946701
0.0525119304657对比上面使用c++ backend的pb来看,反序列化勉强快到2倍,而序列化几乎没有什么特别大的优势。可能得益于使用最新版pb3.10的关系,在google的不断优化下,已经没有那么大差距了吧。因为使用cPython比较麻烦,还会多出不少编译文件。所以没什么提升的情况下,按照个人的需求使用吧。
Reference:
https://github.com/google/protobuf/tree/master/python pb-github库
https://github.com/appnexus/pyrobuf Pyrobuf Library
http://techblog.appnexus.com/blog/2015/12/22/pyrobuf-a-faster-python-protobuf-library-written-in-cython/ pyrobuf-a-faster-python-protobuf-library-written-in-cython