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

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.0p3
SLua 1.3.2
Tolua# github 2017/4/25 19:08:37
XLua 2.1.7
ILRuntime 1.11
Lua: 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分析:

-- Test11
function EmptyFunc()
    _V0 = _V0 + 1
end

_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() - t
end

function Test1(transform)           
    local t = os.clock()
    for i = 1,200000 do
        transform:Rotate(up, 1) 
    end

    return os.clock() - t
end

function 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() - t
end

function Test3()
    local t = os.clock()    

    for i = 1,20000 do              
        GameObject.New()
    end

    return os.clock() - t
end

function 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() - t
end

function Test5()
    local t = os.clock()

    for i = 1,200000 do     
        local p = Input.mousePosition
        --Physics.RayCast
    end

    return os.clock() - t
end

function 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() - t
end

function 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() - t
end

function Test10(trans)
    local t = os.clock()

    for i = 1, 200000 do
        UserClass.TestFunc1(1, "123", trans.position, trans)
    end 

    return os.clock() - t
end

总体效率还是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   
end

function 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() - t
end

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

总结

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

你可能感兴趣的:(Unity引擎)