闲鱼技术01 发表于 2022-11-19 10:43

xLua源码解析——值类型传递无GC

概述:

xLua的FAQ里:
值类型传递会有gc alloc么?
如果你使用的是delegate调用lua函数,或者用LuaTable、LuaFunction的无gc接口,或者数组的话,以下值类型都是没gc的:
1、所有的基本值类型(所有整数,所有浮点数,decimal);
2、所有的枚举类型;
3、字段只包含值类型的struct,可嵌套其它只包含值类型struct;
其中2、3需要把该类型加到GCOptimize。xLua示例Examples->05_NoGc->NoGc.cs展示怎么去避免值类型的GC。下面分析一下,为什么会产生GC,以及xLua如何解决这个问题。
为什么会产生GC

通常来说,只要堆分配了内存,也就是实例化引用对象,在对象使用完时,就会被GC。所以值类型时从栈上分配的,原则上不会产生GC,但是在lua和C#交互过程中,值传递会因为装箱拆箱产生GC。
如何解决

以MyStruct为例:


public struct MyStruct
{
    public MyStruct(int p1, int p2)
    {
      a = p1;
      b = p2;
      c = p2;
      e.c = (byte)p1;
    }
    public int a;
    public int b;
    public decimal c;
    public Pedding e;
}
看下面的MyStruct的部分生成代码(打上了GCOptimize标签)。
// XLuaTest.MyStruct生成代码

static int __CreateInstance(RealStatePtr L)
{
    try
    {
      ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
      if (LuaAPI.lua_gettop(L) == 3 && LuaTypes.LUA_TNUMBER == LuaAPI.lua_type(L, 2) && LuaTypes.LUA_TNUMBER == LuaAPI.lua_type(L, 3))
      {
            int _p1 = LuaAPI.xlua_tointeger(L, 2);
            int _p2 = LuaAPI.xlua_tointeger(L, 3);

            var gen_ret = new XLuaTest.MyStruct(_p1, _p2);
            // 有标签
            // translator.Push(L, gen_ret);
            // 无标签
            translator.PushXLuaTestMyStruct(L, gen_ret);

            return 1;
      }

      if (LuaAPI.lua_gettop(L) == 1)
      {
            // 有标签
            // translator.Push(L, default(XLuaTest.MyStruct));
            // 无标签
            translator.PushXLuaTestMyStruct(L, default(XLuaTest.MyStruct));
            return 1;
      }
    }
    catch (System.Exception gen_e)
    {
      return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return LuaAPI.luaL_error(L, "invalid arguments to XLuaTest.MyStruct constructor!");
}
如果没有打 标签,则translator.PushXLuaTestMyStruct(RealStatePtr L, XLuaTest.MyStruct val)替换为translator.Push(RealStatePtr L, object o)。
可以看出,通用的Push方法的参数是object,这里就会触发装箱的操作。
所以打上后,生成的代码主要就是多了Push、Get、Update三个函数的专用函数。相关代码如下:
int XLuaTestMyStruct_TypeID = -1;
public void PushXLuaTestMyStruct(RealStatePtr L, XLuaTest.MyStruct val)
{
    if (XLuaTestMyStruct_TypeID == -1)
    {
      bool is_first;
      XLuaTestMyStruct_TypeID = getTypeId(L, typeof(XLuaTest.MyStruct), out is_first);

    }

    IntPtr buff = LuaAPI.xlua_pushstruct(L, 25, XLuaTestMyStruct_TypeID);
    if (!CopyByValue.Pack(buff, 0, val))
    {
      throw new Exception("pack fail fail for XLuaTest.MyStruct ,value=" + val);
    }

}

public void Get(RealStatePtr L, int index, out XLuaTest.MyStruct val)
{
    LuaTypes type = LuaAPI.lua_type(L, index);
    if (type == LuaTypes.LUA_TUSERDATA)
    {
      if (LuaAPI.xlua_gettypeid(L, index) != XLuaTestMyStruct_TypeID)
      {
            throw new Exception("invalid userdata for XLuaTest.MyStruct");
      }

      IntPtr buff = LuaAPI.lua_touserdata(L, index); if (!CopyByValue.UnPack(buff, 0, out val))
      {
            throw new Exception("unpack fail for XLuaTest.MyStruct");
      }
    }
    else if (type == LuaTypes.LUA_TTABLE)
    {
      CopyByValue.UnPack(this, L, index, out val);
    }
    else
    {
      val = (XLuaTest.MyStruct)objectCasters.GetCaster(typeof(XLuaTest.MyStruct))(L, index, null);
    }
}

public void UpdateXLuaTestMyStruct(RealStatePtr L, int index, XLuaTest.MyStruct val)
{

    if (LuaAPI.lua_type(L, index) == LuaTypes.LUA_TUSERDATA)
    {
      if (LuaAPI.xlua_gettypeid(L, index) != XLuaTestMyStruct_TypeID)
      {
            throw new Exception("invalid userdata for XLuaTest.MyStruct");
      }

      IntPtr buff = LuaAPI.lua_touserdata(L, index);
      if (!CopyByValue.Pack(buff, 0, val))
      {
            throw new Exception("pack fail for XLuaTest.MyStruct ,value=" + val);
      }
    }

    else
    {
      throw new Exception("try to update a data with lua type:" + LuaAPI.lua_type(L, index));
    }
}
还有Pack和UnPack的方法:
public static void UnPack(ObjectTranslator translator, RealStatePtr L, int idx, out XLuaTest.MyStruct val)
{
    val = new XLuaTest.MyStruct();
    int top = LuaAPI.lua_gettop(L);

    if (Utils.LoadField(L, idx, "a"))
    {

      translator.Get(L, top + 1, out val.a);

    }
    LuaAPI.lua_pop(L, 1);

    if (Utils.LoadField(L, idx, "b"))
    {

      translator.Get(L, top + 1, out val.b);

    }
    LuaAPI.lua_pop(L, 1);

    if (Utils.LoadField(L, idx, "c"))
    {

      translator.Get(L, top + 1, out val.c);

    }
    LuaAPI.lua_pop(L, 1);

    if (Utils.LoadField(L, idx, "e"))
    {

      translator.Get(L, top + 1, out val.e);

    }
    LuaAPI.lua_pop(L, 1);

}

public static bool Pack(IntPtr buff, int offset, XLuaTest.MyStruct field)
{

    if (!Pack(buff, offset, field.a))
    {
      return false;
    }

    if (!Pack(buff, offset + 4, field.b))
    {
      return false;
    }

    if (!Pack(buff, offset + 8, field.c))
    {
      return false;
    }

    if (!Pack(buff, offset + 24, field.e))
    {
      return false;
    }

    return true;
}
public static bool UnPack(IntPtr buff, int offset, out XLuaTest.MyStruct field)
{
    field = default(XLuaTest.MyStruct);

    if (!UnPack(buff, offset, out field.a))
    {
      return false;
    }

    if (!UnPack(buff, offset + 4, out field.b))
    {
      return false;
    }

    if (!UnPack(buff, offset + 8, out field.c))
    {
      return false;
    }

    if (!UnPack(buff, offset + 24, out field.e))
    {
      return false;
    }

    return true;
}

// 额外贴出具体的Pack方法
public static bool Pack(IntPtr buff, int offset, int field)
{
   return LuaAPI.xlua_pack_int32_t(buff, offset, field);
}
总结

上面只举例了struct,其他LuaTable和LuaFunction的无GC方法等,也是同样的原理,通过生成专门的转换方法,避免使用通用方法的objcet类型参数引起的装拆箱。
页: [1]
查看完整版本: xLua源码解析——值类型传递无GC