xLua icon indicating copy to clipboard operation
xLua copied to clipboard

Lua有多种设施来存用户状态, 方便编写thread clean的 LuaEnv, 为啥要用ObjectTranslatorPool加锁的方案?

Open sniper00 opened this issue 2 years ago • 11 comments

  1. 保存到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)
  1. 使用 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;
        }
  1. 使用 LuaRegistry 注册表保存

sniper00 avatar Aug 10 '22 07:08 sniper00

倒和lua侧没关系。 主要是之前实测GCHandle还是挺慢的,大致相当于Key为Object的Dictionary的效率(估计内部也是HashMap实现),当时测试是不如ObjectTranslatorPool的数组方案的。这是非线程安全版本的来由。 而线程版本是在非线程安全版本改的,只加个锁。

chexiongsheng avatar Aug 10 '22 09:08 chexiongsheng

倒和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;
}
  1. 调用C#空函数 1亿次调用
GCHandle cost 515, per 5.15ns/op
Dictionary cost 467, per 4.67ns/op
  1. Lua Call C#空函数大概耗时 20ns/op

  2. 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。

sniper00 avatar Aug 10 '22 10:08 sniper00

但是ObjectTranslatorPool有cache,lastTranslator命中就一个字段读取的开销。但GCHandle只能按字典的性能

chexiongsheng avatar Aug 10 '22 14:08 chexiongsheng

  1. 其实加cache只是上层优化技巧,~~GCCache加一个thread local的LastObject也能达到这种效果~~.关键是核心层要保持thread clean,留有扩展空间.
  2. 我遇到的一个问题是使xlua的ObjectTranslator保持thread clean后, one thread one LuaVM情况下还是出现了线程竞争,造成lua执行出错,并抛出混乱的调用堆栈

sniper00 avatar Aug 11 '22 02:08 sniper00

仔细看了下 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

sniper00 avatar Aug 11 '22 03:08 sniper00

这最开始并不是这样(没有xlua_gl调用)。而且早年的unity GCHandle 支持我记得是有些问题的

chexiongsheng avatar Aug 11 '22 04:08 chexiongsheng

xlua是2017对外开源,但它是2015年初开发的

chexiongsheng avatar Aug 11 '22 04:08 chexiongsheng

这最开始并不是这样(没有xlua_gl调用)。而且早年的unity GCHandle 支持我记得是有些问题的

xlua_gl 是为了从lua~~协程~~非主thread中获取正确标识符

sniper00 avatar Aug 11 '22 04:08 sniper00

这最开始并不是这样(没有xlua_gl调用)。而且早年的unity GCHandle 支持我记得是有些问题的

xlua_gl 是为了从lua协程中获取正确LuaSate

你搞混了概念了。参数带的LuaSate其实标识的就是携程(默认也有个携程,你没自己创建携程实际上就跑在这个默认的携程上)。而xlua_gl获取的是虚拟机公共数据,为了识别出虚拟机。

chexiongsheng avatar Aug 11 '22 04:08 chexiongsheng

这最开始并不是这样(没有xlua_gl调用)。而且早年的unity GCHandle 支持我记得是有些问题的

xlua_gl 是为了从lua协程中获取正确LuaSate

你搞混了概念了。参数带的LuaSate其实标识的就是携程(默认也有个携程,你没自己创建携程实际上就跑在这个默认的携程上)。而xlua_gl获取的是虚拟机公共数据,为了识别出虚拟机。

我更正了下

sniper00 avatar Aug 11 '22 04:08 sniper00

xlua_gl如果在il2cpp下跑应该是蛮快的

chexiongsheng avatar Aug 11 '22 04:08 chexiongsheng