為什么專門開一個坑,來使用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-8
import 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.398800134659
0.707333087921
測試各庫反序列化速度的代碼如下所示:
# coding: utf-8
import 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.492631912231
0.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.085785150528
0.403172016144
0.755691051483 反序列化輸出: 0.090231180191 0.499733924866 0.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 timeit o = """ 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