xLua
xLua copied to clipboard
Lua有多种设施来存用户状态, 方便编写thread clean的 LuaEnv, 为啥要用ObjectTranslatorPool加锁的方案?
- 保存到lua提供的extra space(推荐)
lua源码定义
/*
** thread state + extra space
*/
typedef struct LX {
lu_byte extra_[LUA_EXTRASPACE];
lua_State l;
} LX;
/*
** Main thread combines a thread state and the global state
*/
typedef struct LG {
LX l;
global_State g;
} LG;
private static void SetExtraObject<T>(LuaState L, T obj, bool weak) where T : class
{
var handle = GCHandle.Alloc(obj, weak ? GCHandleType.Weak : GCHandleType.Normal);
IntPtr extraSpace = L - LuaState.Size;
Marshal.WriteIntPtr(extraSpace, GCHandle.ToIntPtr(handle));
}
private static T GetExtraObject<T>(LuaState L) where T : class
{
IntPtr extraSpace = L - LuaState.Size;
IntPtr pointer = Marshal.ReadIntPtr(extraSpace);
var handle = GCHandle.FromIntPtr(pointer);
if (!handle.IsAllocated)
return null;
return (T)handle.Target;
}
SetExtraObject(L, luaEnv, true);
GetExtraObject<LuaEnv>(L)
- 使用
lua_newstate (lua_Alloc f, void *ud)
中的ud参数
private static void SetExtraObject<T>(LuaState L, T obj, bool weak) where T : class
{
var handle = GCHandle.Alloc(obj, weak ? GCHandleType.Weak : GCHandleType.Normal);
IntPtr ud = IntPtr.Zero;
IntPtr fn = LuaAPI.lua_getallocf(L, ref ud);
LuaAPI.lua_setallocf(L, fn, ud);
}
private static T GetExtraObject<T>(LuaState L) where T : class
{
IntPtr ud = IntPtr.Zero;
IntPtr fn = LuaAPI.lua_getallocf(L, ref ud);
var handle = GCHandle.FromIntPtr(ud);
if (!handle.IsAllocated)
return null;
return (T)handle.Target;
}
- 使用 LuaRegistry 注册表保存
倒和lua侧没关系。 主要是之前实测GCHandle还是挺慢的,大致相当于Key为Object的Dictionary的效率(估计内部也是HashMap实现),当时测试是不如ObjectTranslatorPool的数组方案的。这是非线程安全版本的来由。 而线程版本是在非线程安全版本改的,只加个锁。
倒和lua侧没关系。 主要是之前实测GCHandle还是挺慢的,大致相当于Key为Object的Dictionary的效率(估计内部也是HashMap实现),当时测试是不如ObjectTranslatorPool的数组方案的。这是非线程安全版本的来由。 而线程版本是在非线程安全版本改的,只加个锁。
我测试了下GCHandle(保存到lua提供的extra space)和Dictionary效率对比
class Test
{
public LuaState L;
int v = 0;
public Test()
{
L = LuaAPI.luaL_newstate();
SetExtraObject(L, this, true);
}
private static void SetExtraObject<T>(LuaState L, T obj, bool weak) where T : class
{
var handle = GCHandle.Alloc(obj, weak ? GCHandleType.Weak : GCHandleType.Normal);
IntPtr extraSpace = L - LuaState.Size;
Marshal.WriteIntPtr(extraSpace, GCHandle.ToIntPtr(handle));
}
private static T GetExtraObject<T>(LuaState L) where T : class
{
IntPtr extraSpace = L - LuaState.Size;
IntPtr pointer = Marshal.ReadIntPtr(extraSpace);
var handle = GCHandle.FromIntPtr(pointer);
if (!handle.IsAllocated)
return null;
return (T)handle.Target;
}
public static Test FromIntPtr(IntPtr L)
{
Test state = GetExtraObject<Test>(L);
if (state != null)
return state;
return null;
}
public static void Invoke(LuaState L)
{
Test tt = FromIntPtr(L);
for(int i = 0; i < 10;i++)
{
tt.v++;
}
}
public void Invoke2()
{
for (int i = 0; i < 10; i++)
{
v++;
}
}
}
static int Main(string[] args)
{
var test = new Test();
var L = test.L;
const long count = 100000000;
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < count; ++i)
{
Test.Invoke(L);
}
sw.Stop();
Console.WriteLine("GCHandle cost {0}, per {1}ns/op", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000 / (count * 1.0));
}
{
Dictionary<LuaState, Test> dict = new Dictionary<LuaState, Test>();
dict.Add(L, test);
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < count; ++i)
{
dict.TryGetValue(L, out Test t);
t.Invoke2();
}
sw.Stop();
Console.WriteLine("Dictionary cost {0}, per {1}ns/op", sw.ElapsedMilliseconds, sw.ElapsedMilliseconds * 1000000 / (count * 1.0));
}
return 0;
}
- 调用C#空函数 1亿次调用
GCHandle cost 515, per 5.15ns/op
Dictionary cost 467, per 4.67ns/op
-
Lua Call C#空函数大概耗时 20ns/op
-
C#函数内做10次累加
GCHandle cost 1194, per 11.94ns/op
Dictionary cost 1276, per 12.76ns/op
算上Lua Call C#开销后, 两者相差并不大. 如果C#函数内有逻辑, 相差更是微乎其微. GCHandle 方案的好处是保持代码良好的结构, thread clean. 方便实现one thread one LuaVM。
但是ObjectTranslatorPool有cache,lastTranslator命中就一个字段读取的开销。但GCHandle只能按字典的性能
- 其实加cache只是上层优化技巧,~~GCCache加一个thread local的LastObject也能达到这种效果~~.关键是核心层要保持thread clean,留有扩展空间.
- 我遇到的一个问题是使xlua的ObjectTranslator保持thread clean后, one thread one LuaVM情况下还是出现了线程竞争,造成lua执行出错,并抛出混乱的调用堆栈
仔细看了下 ObjectTranslator.Find函数的实现,发现这行调用让lastTranslator的缓存变得意义不大了: var ptr = LuaAPI.xlua_gl(L);
.
raw cost 126, per 1.26ns/op
GCHandle cost 611, per 6.11ns/op
Dictionary with lastcache cost 730, per 7.3ns/op
这最开始并不是这样(没有xlua_gl调用)。而且早年的unity GCHandle 支持我记得是有些问题的
xlua是2017对外开源,但它是2015年初开发的
这最开始并不是这样(没有xlua_gl调用)。而且早年的unity GCHandle 支持我记得是有些问题的
xlua_gl 是为了从lua~~协程~~非主thread中获取正确标识符
这最开始并不是这样(没有xlua_gl调用)。而且早年的unity GCHandle 支持我记得是有些问题的
xlua_gl 是为了从lua协程中获取正确LuaSate
你搞混了概念了。参数带的LuaSate其实标识的就是携程(默认也有个携程,你没自己创建携程实际上就跑在这个默认的携程上)。而xlua_gl获取的是虚拟机公共数据,为了识别出虚拟机。
这最开始并不是这样(没有xlua_gl调用)。而且早年的unity GCHandle 支持我记得是有些问题的
xlua_gl 是为了从lua协程中获取正确LuaSate
你搞混了概念了。参数带的LuaSate其实标识的就是携程(默认也有个携程,你没自己创建携程实际上就跑在这个默认的携程上)。而xlua_gl获取的是虚拟机公共数据,为了识别出虚拟机。
我更正了下
xlua_gl如果在il2cpp下跑应该是蛮快的