最近正在作 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
爲了看到 Lua 和 C++ 的性能差距到底有多大,我又在 quick2d-engine 倉庫中作了一個新的測試例。函數
C++ 版的測試結果是能夠跑到 12000 個星星。能夠看出 cocos2d-x Lua 的綜合性能只有 C++ 的 37.5%。性能
找了好些 Luabinding 庫,根據這個性能測試選擇了 Sol2 。但通過一天多的試驗,這個庫缺乏一個及其重要的特性 concatenate the metatables together。並且做者剛剛發佈 2.5 版後就說他不玩了。。由於太累 -_-#測試
好吧,我換成第二快的 kaguya。ui
這個庫採用大量 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 出來。