http://blog.codingnow.com/2011/12/pbc_lua_binding.html
前几天写的 pbc 初衷就是想可以方便的 binding 到动态语言中去用的。所以今天花了整整一天自己写了个简单的 lua binding 库,就是很自然的工作了。
写完了之后,我很好奇性能怎样,就写了一个非常简单的测试程序测了一下。当然这个测试不说明很多问题,因为测试用的数据实在是太简单了,等明天有空再弄个复杂点的来跑一下吧。我很奇怪,为什么 google 官方的 C++ 版性能这么差。
我的 lua 测试代码大约是这样的:
local protobuf = require "protobuf" addr = io.open("../../build/addressbook.pb","rb") buffer = addr:read "*a" addr:close() protobuf.register(buffer) for i=1,1000000 do local person = { name = "Alice",id = 123,} local buffer = protobuf.encode("tutorial.Person",person) local t = protobuf.decode("tutorial.Person",buffer) end
100 万次的编码和解码在我目前的机器上,耗时 3.8s 。
为了适应性能要求极高的场合,我还提供了另一组高性能 api 。他们可以把数据平坦展开在 lua 栈上,而不构成 table 。只需要把循环里的代码换成
local buffer = protobuf.pack( "tutorial.Person name id","Alice",123) protobuf.unpack("tutorial.Person name id",buffer)
就可以了。这个版本只需要耗时 0.9s 。
一个月前,我曾经自己用 luajit + ffi 实现过一个纯 lua 的版本(没有开源),我跑了一下这个 case ,那个版本也很给力,达到前面的接口的功能,只需要 2.1s 。
不过我相信我新写的 binding 慢主要还是慢在 lua 上, 我换上了 luajit 跑以后,果然快了很多。
table 版本的耗时 1.7s,平坦展开版是 0.57s.
看来 luajit 的优化力度很大。
btw,我去年早些时候还写过一个 lua binding ,今天也顺便测了一下,在 luajit 下跑的时间是 1.2s 。没有这次写的这个版本快。
最后,我随手写了一个 C++ 的版本。应该有不少优化途径。不过我想这也是某中常规用法。
#include <iostream> <sstream<string"addressbook.pb.h" using namespace std; int main(int argc, char* argv[]) { GOOGLE_PROTOBUF_VERIFY_VERSION; for int i=0;i<1000000+{ tutorial::Person person; person.set_name("Alice"); person.set_id(123; stringstream output.SerializetoOstream(&output; output.str; tutorial::Person person2; person2.ParseFromIstream.name.id; } google::protobuf::ShutdownProtobufLibrary; return ; }
这段代码在开了 -O2 编译后,在我的机器上依旧需要时间 1.9s。若是这么看,那简直是太慢了 (比 luajit + c binding 还慢)。很久没研究 C++ 的细节,也懒得看了,如果谁有兴趣研究一下为什么 C++ 这么慢,我很有兴趣知道原因。
12 月 16 日
留言中 lifc0 说这段 C++ 代码中开销最大的是 stringstream 的构造和销毁,所以我改了一段代码:
stringstream output; stringstream input; { output.clear; output""; tutorial; person; person; input; input(output); person2&input; person2}
这样更符合现实应用,每次初始化 stringstream 而不构造新的出来.
这样运行时间就从 1.90s 下降到 1.18s 了.
COMMENTS
不支持扩展吗?
Posted by: kudoo | (34) September 6,2013 12:27 PM
坏处就是要附带.proto 文件,协议容易被破解
Posted by: fdsaf | (33) June 24,2013 05:10 PM
坏处就是要附带.proto 文件,协议容易被破解
Posted by: fdsaf | (32)
在游戏服务器的io序列化中使用protobuf是低效的,测试结果显示更简单直接的序列化代码可以比protobuf快5-10倍。而这些序列化代码,通过协议数据结构定义可以自动生成,很容易就写出这样的工具脚本。如果追求效率,游戏服务器处理消息并不适合使用probobuf。惟一潜在用途是数据库。因为数据结构频繁改动,使得读取老数据有问题,而probobuf则很适合这个场合。
Posted by: pirunxi | (31) May 26,2012 09:30 AM
推荐使用显式的销毁 buffer 的接口。 gc 那个只是 5.2 支持顺手加上的。
Posted by: cloud | (30) February 18,2012 01:29 PM
一个小项目需要lua+protobuf,研究了纯c的nanopb、protobuf-c、upb和lua的lua-pb、lua-protobuf、protoc-gen-lua都不太合适。upb只能decode没encode,其他一些要么需要代码生成,要么没有lua绑定,纯lua的方案则不方便和c代码交换消息。
本打算花点时间给nanopb加上lua绑定,后来在google groups上搜索lightweight、fast等关键字时无意看到http://comments.gmane.org/gmane.comp.lib.protocol-buffers.general/7972,去github研究之后觉得几乎就是我理想中的模式(和nanopb思路差不多,但实现了lua绑定),仔细看原来是云风大侠的作品。再搜相关资料来到这个页面,居然去年还过来踩过一脚。
给pbc提个小建议:lua绑定有几处用setMetatable给table绑定__gc元方法,但lua 5.1似乎只处理userdata的__gc,不知是否5.2做了相关调整,等有空再去研究。
Posted by: lifc0 | (29)
for (int i = 0; i < 1000000; i++)
{
Person person;
person.set_name("Alice");
person.set_id(123);
std::string s = person.SerializeAsstring();
person.ParseFromString(s);
}
Posted by: Anonymous | (28) December 22,2011 02:28 PM
要源码的同学就是不肯去 github 自己取?难道要人打包好 email 才行么?
Posted by: Cloud | (27)
请问能否将你的bind库开源呢?
Posted by: Anonymous | (26)
@tearshark
把一小短测试代码,针对性的优化是没有意义的.
实际不可能这样连着用,因为这样的代码段其实什么事情都没有做.
对于任何语言的任何代码片断,都是以最舒适和直观的写出来为最常规的用法.
对于一个通用库来说尤其如此,因为它是给许多不同的人在不同的场合用的.
就这段代码而言. 一个常规的用法 :
比如在数据编码阶段是,准备一个输出流,准备一个待序列化的结构,装填结构的数据,序列化到流,流输出.
这些步骤在实际用的时候是在不同流程,不同时机去做的,甚至不是一个人来维护,在同一模块里出现.
测试代码应体现这些流程和步骤,而不是想办法放在一起,再看看哪里可以优化,这样得到的优化结果没有太多意义.
Posted by: Cloud | (25) December 18,2011 02:08 AM
for (int i=0;i<1000000;i++) {
output.clear();
output.str(""); //构造/拷贝/析购string,释放内存,分配内存
tutorial::Person person; //构造person
person.set_name("Alice");
person.set_id(123);
person.SerializetoOstream(&output);
input.clear();
input.str(output.str()); //构造/拷贝/析购string,分配内存
tutorial::Person person2; //构造person2
person2.ParseFromIstream(&input);
person2.name();
person2.id();
}
这段代码测试什么的呢?内存分配?
input和output用相同的对象,然后通过seek操作重用数据不更好?
另外,stringstream笨拙的可以,还不如vector<>.clear()----至少我见过的vector<>的实现,clear()都不会真正的删除内存.
如果把stringstream替换成vector<>的实现,则我心目中理想的写法是
vectorstream<char> input;
tutorial::Person person;
tutorial::Person person2;
for (int i=0;i<1000000;i++) {
input.clear();
person.set_name("Alice");
person.set_id(123);
person.SerializetoOstream(&input);
input.seek(0);
person2.ParseFromIstream(&input);
person2.name();
person2.id();
}
这样可以尽量避免input的反复内存分配导致的效率低下. C++真不是适合新手使用的库,到处都是陷阱,特别的STL的IO实现部分.还不如C.
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。