Unity中SLua、Tolua、XLua和ILRuntime效率评测

来源:互联网 发布:java变量类型怎么赋值 编辑:程序博客网 时间:2024/05/17 02:20

Unity脚本效率评测

对SLua、Tolua、XLua和ILRuntime四个脚本插件进行效率测试,对框架脚本进行选型。
本文项目:https://github.com/cateatcatx/UnityScriptPTest
tolua:https://github.com/topameng/tolua
slua:https://github.com/pangweiwei/slua
xlua:https://github.com/Tencent/xLua
ILRuntime:https://github.com/Ourpalm/ILRuntime

用例版本

Unity5.6.0p3SLua 1.3.2Tolua# github 2017/4/25 19:08:37XLua 2.1.7ILRuntime 1.11Lua: luajit-2.1.0-beta2

测试环境

Smartian T2、Win7(64bit)

实验方案

总共设计了17个Test,分别从以下3个方面来考察脚本效率(JIT和非JIT),实验结果取10次的平均值,时间单位为ms。通过实验数据简单分析原因(Lua插件会横向对比,ILRuntime会单独考虑,因为毕竟C#和Lua本身差别较大)。

    1. Mono -> Script,Mono调用脚本    2. Script -> Mono,脚本调用Mono    3. Script自身,脚本自身执行效率

Mono -> Script

Test11 Test12 Test13 Test14 Test15 Test16 Sum Tolua 186 449 598 407 577 759 2978 SLua 315 751 901 757 1253 1883 5863 XLua 145 924 1010 573 1507 1929 6091 ILRuntime 711 422 368 379 397 393 2672 Tolua(JIT) 168 454 592 416 578 826 3037 SLua(JIT) 384 842 956 824 1328 3439 7775 XLua(JIT) 189 957 1047 608 1540 1700 6043 ILRuntime(JIT) 1232 892 903 969 1175 1102 6275

Lua分析:

-- Test11function EmptyFunc()    _V0 = _V0 + 1end_V0 = 1 -- Test12_V1 = "12345" -- Test13_V2 = GameObject.New() -- Test14_V3 = Vector3.New(1, 2, 3) -- Test15_V4 = {1, 2, 3} -- Test16

Test11为Mono调用脚本中空方法,Test12~16为测试Mono到脚本中取变量(ILRuntime代码类似,访问类的static函数与变量)。ILRuntime因为没有变量类型的转换所以效率最为优秀(JIT模式下使用的是Mono的反射取值所以会更慢,ILRuntime内部可能对类型变量有缓存,所以比反射快很多),Lua中可以看到Tolua的综合性能尤为突出(所有测试均好于其他Lua)。
对比Tolua和SLua的实现发现,Tolua会尽量减少与C++通信的次数,因为c#与c++通信会有一定效率损耗(参数的Marshaling等),虽然是Mono与Lua通信,但是其中还夹着一层C++,所以Mono与Lua通信的主要优化思路就是减少与C++的通信,从实验数据来看Tolua的这种优化效果是很明显的。

// SLua的函数调用public bool pcall(int nArgs, int errfunc){    if (!state.isMainThread())    {        Logger.LogError("Can't call lua function in bg thread");        return false;    }    LuaDLL.lua_getref(L, valueref);    if (!LuaDLL.lua_isfunction(L, -1))    {        LuaDLL.lua_pop(L, 1);        throw new Exception("Call invalid function.");    }    LuaDLL.lua_insert(L, -nArgs - 1);    if (LuaDLL.lua_pcall(L, nArgs, -1, errfunc) != 0)    {        LuaDLL.lua_pop(L, 1);        return false;    }    return true;}// Tolua的函数调用public void Call(){     BeginPCall();     PCall();     EndPCall();}public int BeginPCall(int reference){                            return LuaDLL.tolua_beginpcall(L, reference);}public int LuaPCall(int nArgs, int nResults, int errfunc){    return LuaDLL.lua_pcall(L, nArgs, nResults, errfunc);}public void LuaSetTop(int newTop){    LuaDLL.lua_settop(L, newTop);}

对比SLua和Tolua代码的函数调用部分,发现SLua的C++调用多余Tolua两倍左右,所以效率高下立见。变量读取读者可以自行对比,总结就是Tolua通过减少C++调用的方式来优化效率,后期对Lua的进一步优化也要遵循这个思路。


ILRuntime分析:
实验发现解释执行下,ILRuntime的获取变量的效率要明显好于Lua,主要是因为都是C#对象,不需要进行类型转换。ILRuntime的JIT模式其实是用的Mono的,效率反而比解释执行更低,猜测是因为JIT下主要采用反射调用函数和取变量,而ILRuntime的解释器可能内部有缓存(因为没看过实现,都是不负责任猜测)。

Script->Mono

Test0 Test1 Test2 Test3 Test4 Test5 Test6 Test7 Test10 Sum Tolua 675 797 1410 354 839 343 407 1681 915 7426 SLua 768 640 2305 466 1110 408 394 3379 1299 10774 XLua 590 648 8504 450 775 1213 695 1267 845 14989 ILRuntime 1152 1054 4012 315 998 437 1026 3272 1434 13703 Tolua(JIT) 612 701 7.4 357 823 318 37 128 884 3871 SLua(JIT) 732 679 5.4 465 1197 411 10 165 1307 4974 XLua(JIT) 636 668 8312 438 767 1303 734 1340 911 15113 ILRuntime(JIT) 72 197 84 260 402 33 219 303 77 1651

Lua分析:

function Test0(transform)       local t = os.clock()    for i = 1,200000 do        transform.position = transform.position    end    return os.clock() - tendfunction Test1(transform)               local t = os.clock()    for i = 1,200000 do        transform:Rotate(up, 1)     end    return os.clock() - tendfunction Test2()        local t = os.clock()    for i = 1, 2000000 do        local v = Vector3.New(i, i, i)        local x,y,z = v.x, v.y, v.z    end    return os.clock() - tendfunction Test3()    local t = os.clock()        for i = 1,20000 do                      GameObject.New()    end    return os.clock() - tendfunction Test4()        local t = os.clock()    local tp = typeof(SkinnedMeshRenderer)    for i = 1,20000 do                      local go = GameObject.New()        go:AddComponent(tp)        local c = go:GetComponent(tp)        c.receiveShadows=false    end    return os.clock() - tendfunction Test5()    local t = os.clock()    for i = 1,200000 do             local p = Input.mousePosition        --Physics.RayCast    end    return os.clock() - tendfunction Test6()        local Vector3 = Vector3     local t = os.clock()    for i = 1, 200000 do        local v = Vector3.New(i,i,i)        Vector3.Normalize(v)    end    return os.clock() - tendfunction Test7()            local Quaternion = Quaternion    local t = os.clock()    for i=1,200000 do        local q1 = Quaternion.Euler(i, i, i)                local q2 = Quaternion.Euler(i * 2, i * 2, i * 2)        Quaternion.Slerp(Quaternion.identity, q1, 0.5)          end    return os.clock() - tendfunction Test10(trans)    local t = os.clock()    for i = 1, 200000 do        UserClass.TestFunc1(1, "123", trans.position, trans)    end     return os.clock() - tend

总体效率还是Tolua胜出,其中XLua在Test2中较比SLua、Tolua差出不止一个数量级,主要是因为Tolua和SLua对于Unity的值类型变量做了lua的实现,这种值类型SLua和Tolua中是一个table,而在XLua中是一个Userdata,所以SLua和Tolua在做Test2的时候并没有跟Unity交互(从JIT结果也能看出来,JIT不能处理C函数,所以JIT后Test2效果提升明显),而XLua需要频繁和Unity交互,效率消耗明显。对于对象类型的变量,3种lua处理机制是雷同的,只是内部实现细节不一样而已,细节不再本文讨论范围内,从实验数据上来看,还是Tolua的内部实现更加效率。用好lua+unity,让性能飞起来——lua与c#交互篇,这篇文章对C#与Lua的交互原来有非常详细的说明,虽然插件后续有改进,但是核心思想还是不变的。


ILRuntime分析:
数据上来看ILRuntime解释器的效率还是很高的并不比lua慢太多,但是对于Vector3这种Unity值类型的处理跟lua差距比较大(主要是因为SLua和Tolua中的Unity值类型其实就是table,等于没有跟Unity交互)。ILRuntime还是一个很有潜力的Unity热更解决方案的,毕竟C#配合VS的开发效率还是比Lua高不少的。其中的JIT部分是Mono层的,跟本身的C#代码是没有区别的,不参与对比。

Script自身

Test8 Test9 Sum Tolua 254 4246 4500 SLua 255 4766 5022 XLua 311 4506 4817 ILRuntime 852 79048 79900 Tolua(JIT) 46 371 417 SLua(JIT) 48 414 463 XLua(JIT) 40 469 510 ILRuntime(JIT) 222 313 536
function Test8()    local total = 0    local t = os.clock()    for i = 0, 1000000, 1 do        total = total + i - (i/2) * (i + 3) / (i + 5)    end    return os.clock() - t   endfunction Test9()    local array = {}    for i = 1, 1024 do        array[i] = i    end    local total = 0    local t = os.clock()    for j = 1, 100000 do        for i = 1, 1024 do            total = total + array[i]        end             end    return os.clock() - tend

因为Lua全部使用的是LuaJIT2.1.0B2版本,所以其实脚本自身的效率理论上应该是一致的,从数据上看也差不多。实验结果上主要体现了Lua解释器的速度要明显好于ILRuntime(语言内部实现不一样,勉强对比在一块,毕竟lua是c写的),并且发现LuaJIT对效率的提升也是好几个数量级,虽然LuaJIT很多坑,但是如果能用好还是个优化利器。

总结

综合来看Tolua是现在效率较好的Unity Lua解决方案,后续会对Tolua的内部实现做进一步剖析,从来做进一步的效率优化。

原创粉丝点击