学习protobuf

一、认识Protobuf

ref:http://blog.csdn.net/program_think/article/details/4229773
摘要:
1. protobuf是一个开源项目。
2. 用于把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输协议格式。
3. 优点:◇性能好/效率高◇代码生成机制◇支持“向后兼容”和“向前兼容”◇支持多种编程语言
4. 缺点:◇应用不够广◇二进制格式导致可读性差,定位问题难◇缺乏自描述

使用它的最大理由应该是“代码生成”,也就是只要写好描述脚本,它可以自动生成c++,java,或其他语言的类,自带序列化和反序列化功能,极大的方便了开发者,不再需要手动写数据存储类。另外就是序列化的密度和压缩比大;以及不同语言的序列化结果一致,使得不同语言可一致处理。

二、安装Python Protobuf

1. pip install protobuf
这将安装python对protobuf的支持库,正常情况下可能只是安装pure python的支持,效率不如c扩展。为了安装c扩展,需要执行:
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp \
pip install protobuf
会编译安装c扩展,如果有编译错误,需要解决。

2. 安装protobuf 代码生成器 -- protoc
在官网(http://code.google.com/p/protobuf/)下载安装包或源代码包,直接安装或者编译(按readme提示)安装。

三、示例
建立addressbook.proto文件:
package tutorial;

message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}

message AddressBook {
  repeated Person person = 1;
}
执行命令:
/usr/local/bin/protoc -I=./ --python_out=./ ./addressbook.proto
将在同目录下生成addressbook_pb2.py文件,建立protobuf_test.py文件:
def testProto():
    import addressbook_pb2
    person = addressbook_pb2.Person()
    person.id = 1234
    person.name = "John Doe"
    person.email = "jdoe@example.com"
    phone = person.phone.add()
    phone.number = "555-4321"
    phone.type = addressbook_pb2.Person.HOME

    print 'src:', person
    str = person.SerializeToString()
    person2 = addressbook_pb2.Person()
    person2.ParseFromString(str)

    print 'Serialize len:%d' % (len(str))
    print 'dst:',person2

if __name__ == '__main__':
    testProto()

运行,显示如下:
src: name: "John Doe"
id: 1234
email: "jdoe@example.com"
phone {
  number: "555-4321"
  type: HOME
}

Serialize len:45
dst: name: "John Doe"
id: 1234
email: "jdoe@example.com"
phone {
  number: "555-4321"
  type: HOME
}

可见序列化和反序列化成功。

四、提高

ref:http://yz.mit.edu/wp/fast-native-c-protocol-buffers-from-python/
摘要:
1.写了一段python测试protobuf序列化和反序列化的时间消耗。
2.使用c扩展和纯python库执行的性能对比,序列化15倍,反序列化8倍。
3.使用protoc直接生成proto的c++代码,封装c++代码生成c扩展,安装c扩展。在测试代码中import这个c扩展,执行测试代码将自动使用c扩展版本进行数据存储和处理,性能将得到进一步提高,相比纯python库,序列化68倍,反序列化13倍。

例:
/usr/local/bin/protoc -I=./ --cpp_out=./ ./addressbook.proto
将生成addressboo.pb.h和addressboo.pb.cc文件。

建立addressbook.c文件:
#include <Python.h>

static PyMethodDef podMethods[] = {
  {NULL, NULL, 0, NULL}        /* Sentinel */
};

PyMODINIT_FUNC
initpodpb(void)
{
  PyObject *m;

  m = Py_InitModule("podpb", podMethods);
  if (m == NULL)
    return;
}

建立setup.py文件:
import os
import platform

from setuptools import setup, find_packages
from distutils.core import setup, Extension

setup(
    ext_modules=[Extension('podpb',
sources=['./addressbook.c','./addressbook.pb.cc'], libraries=['protobuf'])]
    )

执行python setup.py build命令,有以下输出:
g++ -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions build/temp.linux-x86_64-2.6/./addressbook.o build/temp.linux-x86_64-2.6/./addressbook.pb.o -lprotobuf -o build/lib.linux-x86_64-2.6/podpb.so
表示生成了对应的c扩展podpb.so,将这个so文件复制到当前目录下。

在protobuf_test.py加入测试代码:
import os, random
import timeit
#import podpb

def ser(xs):
    return [x.SerializeToString() for x in xs]

def parse(ys):
    import addressbook_pb2
    for y in ys: addressbook_pb2.Person().ParseFromString(y)

def benchTest():
    import addressbook_pb2

    nruns = 1000
    nwarmups = 100

    xs = [] # your protobufs
    for i in range(10):
        rId = random.randint(100000, 999999)
        person = addressbook_pb2.Person()
        person.id = rId
        person.name = "John Doe"
        person.email = "jdoe@example.com"
        phone = person.phone.add()
        phone.number = "555-4321"
        phone.type = addressbook_pb2.Person.HOME

        xs.append(person)

    t = timeit.Timer(lambda:None)
    t.timeit(nwarmups)
    print 'noop:', t.timeit(nruns) / nruns

    t = timeit.Timer(lambda: ser(xs))
    t.timeit(nwarmups)
    print 'ser:', t.timeit(nruns) / nruns / len(xs)

    ys = ser(xs)
    t = timeit.Timer(lambda: parse(ys))
    t.timeit(nwarmups)
    print 'parse:', t.timeit(nruns) / nruns / len(xs)

    print 'msg size:', sum(len(y) for y in ys) / len(ys)

if __name__ == '__main__':
    testProto()
    benchTest()
   
未使用podpb及PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION,有:
noop: 9.29832458496e-08
ser: 2.62283086777e-05
parse: 3.05251121521e-05
msg size: 46

使用PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION有:
noop: 9.08374786377e-08
ser: 2.59931087494e-06
parse: 7.11431503296e-06
msg size: 46

使用podpb及PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION有:
noop: 9.20295715332e-08
ser: 7.13109970093e-07
parse: 4.77969646454e-06
msg size: 46

和文章对比性能差别不那么大,但趋势是符合的。

posted @ 2013-08-13 12:43  zhiwen  阅读(1452)  评论(1编辑  收藏  举报