代码里的几个标签解释:
· XLua.GCOptimize: gc代码优化。对于一个c#纯值类型(官网指一个只包含值类型的struct,可以嵌套其它只包含值类型的struct)或者c#枚举值加上了这个配置,会使得该类型在lua和c#间传递不产生gc alloc,该类型的数组访问也不会产生gc。
(除枚举之外,包含无参构造函数的复杂类型,都会生成lua table到该类型,以及该类型的一维数组转换代码,可以优化转换性能)
查了很多资料才理解括号内句子的意思,要理解gz代码优化,首先要知道gc是如何产生的
网上的经典例子,我觉得很易懂,直接搬过来用了:
int inc1(int i)
{
return i+1;
}
object inc2(object o)
{
return (int)o + 1;
}
对比这两个函数,inc1的性能是inc2的20倍。差距这么大的原因在于参数及其返回类型。inc2的参数是 object,意味着一个值类型传入需要boxing,具体一点就是在堆上申请一块内存,把类型信息,值拷贝进去,要使用的时候需要unboxing,就是从刚刚那堆内存拷贝到栈上,等函数执行完毕后,这个堆内存被gc检测到已经没引用,释放该内存。
而这个20倍只是一个参数加一个返回的情况,随着参数增多,函数变得复杂,这个差距会更大。而且GC比较难控制,Unity的手游项目,GC往往是卡顿的元凶。对于lua和c#间交互的gc优化,或者值类型优化,实际都是在避免inc2的情况发生。
XLua的解决方法:你告诉我用什么参数调用,我来优化
[CSharpCallLua]
public delegate int Inc(int i);
public int inc(int p){
return p
}
Inc func = luaenv.Global.Get("inc");
int num = func(666);
代码分析: 1. 按你的需要声明一个delegate,打上CSharpCallLua标签
2. 用table的Get接口把inc函数映射到func委托
3. 多复杂的参数都是和上面一样:声明/获取/使用
4. 另外XLua还支持一个lua table映射到一个c# interface,对这个interface的属性访问会访问到lua table的对应字段,成员方法会调用到lua table里对应函数,且也是无gc的。对应demo5里的代码:
[CSharpCallLua]
public delegate void ArrayAccess(Array arr);
[CSharpCallLua]
public interface IExchanger{
void exchange(Array arr);
}
ArrayAccess farr;
IExchanger ie; //接口用来映射lua里的table
luaenv.DoString(@"
function array_exchange(arr)
arr[0],arr[1] = arr[1],arr[0]
end
exchanger = {
exchange = function(self,arr)
array_exchange(arr)
end
}
")
luaenv.Global.Get("array_exchange",out farr);luaenv.Global.Get("exchanger",out ie);
//lua调用c#值但无gc
farr(a1);
//c#用interface调lua
ie.exchange(a2);
XLua复杂值类型优化:
复杂值产生的问题:每一次值类型放入对象池中就会碰到inc2的情况,会boxing成一个新的对象,伴有入池的一系列操作
解决:Xlua是这么优化的-->值拷贝。从c#传递到lua本质上是把值拷贝到lua table,避免入池而避免inc2;简单类型也是一个c#的int传入lua,也是直接把int值拷贝到lua的栈上。
原理[铺垫到这里才真正解释了开篇括号的内容]:
1. 生成struct的值拷贝代码,用于把struct里的各字段拷贝到一块非托管内存(Pack),以及从非托管内存拷贝出各字段的值(UnPack)
2. c#传struct到lua:调用lua的api,申请一块userdata(对于c#来说是非托管代码),调用pack把struct打包
3. lua回传到给c#:调用unpack解出struct
4. 实质上就是把c#数据结构序列化到一块内存以及从内存反序列化回来
优点:
1. 操作简单,声明一下GCOptimize即可,之所以要声明,是避免生成代码过多
2. 相比table方案更省内存。实测实table方案1/3
其它值类型GC优化:
下面大多数优化都只在xLua有效,可以在其05_NoGc示例看到用法,生成代码后运行在profiler看你效果。
1、枚举类型传递无GC;
2、decimal不丢失精度而且无GC;
3、所有无GC的类型,它的数组访问没有GC,这个貌似大多数方案都做到;
4、能被GCOptimize优化的struct,在Lua可以直接传一个对应结构的table,无GC;
5、LuaTable提供一系列泛化Get/Set接口,可以传递值类型而无GC;
6、一个interface加入到CSharpCallLua后,可以用table来实现这个interface,通过这interface访问table无GC;附一点demo5里的注释:
//设置当前对象的lua脚本
luaenv.Global.Set("monoBehaviour",this);
//获取lua中的全局方法映射到delegate中
luaenv.Global.Get("id",out f1);
//f1 = luaenv.Global.Get<IntParam>("id");
luaenv.Global.Get("id",out f2);
//f2 = luaenv.Global.Get<Vector3Param>("id");
//c#类型加了这个配置,xLua会生成这个类型的适配代码(包括构造该类型实例,成员属性、方法等等),不然就会用性能较低的反射方式访问
[LuaCallCSharp]
//如果希望把一个lua函数适配到一个c# delegate(c#侧是各种回掉:UI事件,delegate参数),或者把一个lua table适配到一个c# interface,需要加上该配置
[CSharpCallLua]