前几天写的 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 了.