爲 cocos2d-x 選擇新的 Luabinding (1)

最近正在作 Cocos Creator 的 Lua 支持。由於 Creator 使用了一個精簡的 cocos2d-x 引擎,因此我也基於這個引擎來作 Cocos Creator 的 Lua 支持。node

cocos2d-x 目前使用一套基於 tolua++ 的 Luabinding 層。而 tolua++ 這貨已經中止維護快十年了,因此換掉 tolua++ 勢在必行。另外我一直認爲 tolua++ 存在性能問題,替換爲更高效的 luabinding 能夠充分發揮 Lua 的性能優點。c++

這篇文章的重點就是選擇新的 Luabinding。git


性能測試

既然以提升性能爲目的,必須用數據來講話。因此我作了一個性能測試工程,放在 github 的 cocos2dx_benchmark 倉庫裏。github

在 iPhone 6 上,這個 Lua 測試例能夠在保證 55fps+ 幀率的基礎上跑 4500 個星星。app

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 版後就說他不玩了。。由於太累 -_-#測試

好吧,我換成第二快的 kaguyaui

這個庫採用大量 C++11 的新特性,採用模板的形式來導出 C++ 接口,代碼以下:lua

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 出來。

相關文章
相關標籤/搜索