您的位置:首页 > 编程语言 > Lua

pbc 库的 lua binding

2013-11-27 22:32 253 查看


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>
#include <sstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;

int main(int argc, char* argv[]) {
GOOGLE_PROTOBUF_VERIFY_VERSION;

for (int i=0;i<1000000;i++) {
tutorial::Person person;

person.set_name("Alice");
person.set_id(123);

stringstream output;

person.SerializeToOstream(&output);
output.str();
tutorial::Person person2;

person2.ParseFromIstream(&output);

person.name();
person.id();
}

google::protobuf::ShutdownProtobufLibrary();

return 0;
}

这段代码在开了 -O2 编译后,在我的机器上依旧需要时间 1.9s。若是这么看,那简直是太慢了 (比 luajit + c binding 还慢)。很久没研究 C++ 的细节,也懒得看了,如果谁有兴趣研究一下为什么 C++ 这么慢,我很有兴趣知道原因。
12 月 16 日
留言中 lifc0 说这段 C++ 代码中开销最大的是 stringstream 的构造和销毁, 所以我改了一段代码:
stringstream output;
stringstream input;

for (int i=0;i<1000000;i++) {
output.clear();
output.str("");

tutorial::Person person;
person.set_name("Alice");
person.set_id(123);

person.SerializeToOstream(&output);

input.clear();
input.str(output.str());

tutorial::Person person2;

person2.ParseFromIstream(&input);

person2.name();
person2.id();
}

这样更符合现实应用, 每次初始化 stringstream 而不构造新的出来.
这样运行时间就从 1.90s 下降到 1.18s 了.

云风 提交于 December 14, 2011 10:44 PM | 固定链接


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) June 24, 2013 05:09 PM

在游戏服务器的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) February 18, 2012 09:19 AM

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) December 22, 2011 10:39 AM

请问能否将你的bind库开源呢?

Posted by: Anonymous | (26) December 22, 2011 09:33 AM

@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.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: