微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

pbc 库的 lua binding

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

不支持扩展吗?

坏处就是要附带.proto 文件,协议容易被破解

坏处就是要附带.proto 文件,协议容易被破解

在游戏服务器的io序列化中使用protobuf是低效的,测试结果显示更简单直接的序列化代码可以比protobuf快5-10倍。而这些序列化代码,通过协议数据结构定义可以自动生成,很容易就写出这样的工具脚本。如果追求效率,游戏服务器处理消息并不适合使用probobuf。惟一潜在用途是数据库。因为数据结构频繁改动,使得读取老数据有问题,而probobuf则很适合这个场合。

推荐使用显式的销毁 buffer 的接口。 gc 那个只是 5.2 支持顺手加上的。

一个小项目需要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做了相关调整,等有空再去研究。

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);
}

要源码的同学就是不肯去 github 自己取?难道要人打包好 email 才行么?

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

@tearshark

把一小短测试代码,针对性的优化是没有意义的.

实际不可能这样连着用,因为这样的代码段其实什么事情都没有做.

对于任何语言的任何代码片断,都是以最舒适和直观的写出来为最常规的用法.

对于一个通用库来说尤其如此,因为它是给许多不同的人在不同的场合用的.

就这段代码而言. 一个常规的用法 :

比如在数据编码阶段是,准备一个输出流,准备一个待序列化的结构,装填结构的数据,序列化到流,流输出.

这些步骤在实际用的时候是在不同流程,不同时机去做的,甚至不是一个人来维护,在同一模块里出现.

测试代码应体现这些流程和步骤,而不是想办法放在一起,再看看哪里可以优化,这样得到的优化结果没有太多意义.

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 举报,一经查实,本站将立刻删除。

相关推荐