2

最近正在做 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 个星星。

benchmark_lua_cocos.png

为了看到 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<Ref>()
    .addMemberFunction("retain", &Ref::retain)
    .addMemberFunction("release", &Ref::release)
    .addMemberFunction("getReferenceCount", &Ref::getReferenceCount));

cc["Scheduler"].setClass(kaguya::ClassMetatable<Scheduler, Ref>());

cc["Director"].setClass(kaguya::ClassMetatable<Director, Ref>()
    .addStaticFunction("getInstance", &Director::getInstance)
    .addMemberFunction("endDirector", &Director::end)
    .addMemberFunction("getScheduler", &Director::getScheduler));

cc["Node"].setClass(kaguya::ClassMetatable<Node, Ref>()
    .addStaticFunction("create", &Node::create)
    .addMemberFunction("addChild", static_cast<void(Node::*)(Node*)>(&Node::addChild))
    .addMemberFunction("addChild", static_cast<void(Node::*)(Node*, int)>(&Node::addChild))
    .addMemberFunction("addChild", static_cast<void(Node::*)(Node*, int, int)>(&Node::addChild))
    .addMemberFunction("addChild", static_cast<void(Node::*)(Node*, int, const std::string&)>(&Node::addChild))
    .addMemberFunction("removeChild", &Node::removeChild)
    .addMemberFunction("setPosition", static_cast<void(Node::*)(const Vec2&)>(&Node::setPosition))
    .addMemberFunction("setPosition", static_cast<void(Node::*)(float, float)>(&Node::setPosition))
    .addMemberFunction("setColor", &Node::setColor)
    .addMemberFunction("setOpacity", &Node::setOpacity)
    .addMemberFunction("schedule", static_cast<void(Node::*)(const std::function<void(float)>&, float, const std::string &)>(&Node::schedule)));

代码看上去是相当整洁的。

但经过性能测试,却发现在超过 3000 个星星后,性能会出现暴跌,而不是线性降低。

又反复测试了一下,估计问题应该出在 Lua call C++ member function 这里。所以又写了一段代码:


static int lgetNode(lua_State *L)
{
    // node
    kaguya::ObjectPointerWrapper<Sprite*> *wrap = static_cast<kaguya::ObjectPointerWrapper<Sprite*>*>(lua_touserdata(L, -1));
    Node *node = static_cast<Node*>(wrap->get());
    lua_pushlightuserdata(L, node);
    return 1;
}

static int lsetPosition(lua_State *L)
{
    // node, x, y
    Node *node = static_cast<Node*>(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<Node*>(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 出来。


dualface
38 声望28 粉丝

FleaPHP/QeePHP/Quick-Cocos2d-X/Quick2d/GameBox Cloud 开源项目创始人,连续创业者。