最近正在做 Cocos Creator 的 Lua 支持。因为 Creator 使用了一个精简的 cocos2d-x 引擎,所以我也基于这个引擎来做 Cocos Creator 的 Lua 支持。
cocos2d-x 目前使用一套基于 tolua++ 的 Luabinding 层。而 tolua++ 这货已经停止维护快十年了,所以换掉 tolua++ 势在必行。另外我一直认为 tolua++ 存在性能问题,替换为更高效的 luabinding 可以充分发挥 Lua 的性能优势。
这篇文章的重点就是选择新的 Luabinding。
性能测试
既然以提高性能为目的,必须用数据来说话。所以我做了一个性能测试工程,放在 github 的 cocos2dx_benchmark 仓库里。
在 iPhone 6 上,这个 Lua 测试例可以在保证 55fps+ 帧率的基础上跑 4500 个星星。
为了看到 Lua 和 C++ 的性能差距到底有多大,我又在 quick2d-engine 仓库中做了一个新的测试例。
C++ 版的测试结果是可以跑到 12000 个星星。可以看出 cocos2d-x Lua 的综合性能只有 C++ 的 37.5%。
选择新 Luabinding
找了好些 Luabinding 库,根据这个性能测试选择了 Sol2 。但经过一天多的试验,这个库缺少一个及其重要的特性 concatenate the metatables together。而且作者刚刚发布 2.5 版后就说他不玩了。。因为太累 -_-#
好吧,我换成第二快的 kaguya。
这个库采用大量 C++11 的新特性,采用模板的形式来导出 C++ 接口,代码如下:
auto cc = _lua["cc"] = _lua.newTable();
cc["Ref"].setClass(kaguya::ClassMetatable()
.addMemberFunction("retain", &Ref::retain)
.addMemberFunction("release", &Ref::release)
.addMemberFunction("getReferenceCount", &Ref::getReferenceCount));
cc["Scheduler"].setClass(kaguya::ClassMetatable());
cc["Director"].setClass(kaguya::ClassMetatable()
.addStaticFunction("getInstance", &Director::getInstance)
.addMemberFunction("endDirector", &Director::end)
.addMemberFunction("getScheduler", &Director::getScheduler));
cc["Node"].setClass(kaguya::ClassMetatable()
.addStaticFunction("create", &Node::create)
.addMemberFunction("addChild", static_cast(&Node::addChild))
.addMemberFunction("addChild", static_cast(&Node::addChild))
.addMemberFunction("addChild", static_cast(&Node::addChild))
.addMemberFunction("addChild", static_cast(&Node::addChild))
.addMemberFunction("removeChild", &Node::removeChild)
.addMemberFunction("setPosition", static_cast(&Node::setPosition))
.addMemberFunction("setPosition", static_cast(&Node::setPosition))
.addMemberFunction("setColor", &Node::setColor)
.addMemberFunction("setOpacity", &Node::setOpacity)
.addMemberFunction("schedule", static_cast&, float, const std::string &)>(&Node::schedule)));
代码看上去是相当整洁的。
但经过性能测试,却发现在超过 3000 个星星后,性能会出现暴跌,而不是线性降低。
又反复测试了一下,估计问题应该出在 Lua call C++ member function 这里。所以又写了一段代码:
static int lgetNode(lua_State *L)
{
// node
kaguya::ObjectPointerWrapper *wrap = static_cast*>(lua_touserdata(L, -1));
Node *node = static_cast(wrap->get());
lua_pushlightuserdata(L, node);
return 1;
}
static int lsetPosition(lua_State *L)
{
// node, x, y
Node *node = static_cast(lua_touserdata(L, -3));
node->setPosition(lua_tonumber(L, -2), lua_tonumber(L, -1));
return 0;
}
static int lsetOpacity(lua_State *L)
{
// node, opacity
Node *node = static_cast(lua_touserdata(L, -2));
node->setOpacity(lua_tointeger(L, -1));
return 0;
}
lua_State *L = _lua.state();
lua_pushcfunction(L, &lgetNode);
lua_setglobal(L, "lgetNode");
lua_pushcfunction(L, &lsetPosition);
lua_setglobal(L, "lsetPosition");
lua_pushcfunction(L, &lsetOpacity);
lua_setglobal(L, "lsetOpacity");
在 Lua 里,就不再使用对象方法来调用了,而是改成这三个全局函数。测试结果暴涨到 8200 个星星,接近 C++ 70% 的性能了。
总结
cocos2d-x 现在使用的 Luabinding 只能达到 C++ 37.5% 的性能。在更换新 Luabinding 并做相应调整后,可以获得高达 C++ 70% 的性能表现。而这损耗的 30%,其中还有大量的数值运算。要知道拼计算,脚本是肯定比不过 C++ 的。
虽然新的 Luabinding 方案还有很多工作要做,但我有信心最终搞一个比现在快一倍的 Luabinding 出来。