Lua数据的内存结构

2021-11-04 10:50:07 浏览数 (1)

基本类型

Lua中每个数据类型都是一个TValue

  • value_:Value是个共用体,一共占8字节,根据实际类型选择具体是哪个字段
  • tt_:是用来表示上面的共用体实际是哪个类型,占4字节

可以看到基本类型(浮点数,整数,布尔值,lightuserdata,C 函数)至少会占用 12字节 (内存对齐后16字节)

gc这个指针指Lua虚拟机托管的对象包括字符串,Table,Userdata,协程,闭包,Proto等,内存由虚拟机额外分配并托管,下面具体说

GC对象(字符串,Userdata,协程,Proto)

每个GC对象都有个公有的头,next表示全局gc池的下一个节点的指针,将所有的gc对象都链起来 (PS:对比ue4是使用一个全局Object数组实现的,Lua每个节点就浪费掉8字节) tt是当前对象的类型,和上面的tt_是一样的 marked是给垃圾回收器用的标记位

因此,GC对象至少会占用10字节的头部内存

String字符串

  • extra:是标记长字符串是否做过hash(这个字段短字符串没用到)
  • shrlen:短字符串的长度,由于是1字节,所以这个长度最多不超过256,也就是说短字符串理论最长可以调到256个字符(默认短字符串是40,这个字段长字符串没用到)
  • hash:是这个字符串算出来的hash值
  • u:是一个共用体,分两种情况: 短字符串用来标记下一个字符串的指针,因为短字符串全局唯一,所以lua内部是通过一个链表把所有字符串连接起来的 (PS:对比UE4的FName,是通过一个全局数组实现的,Lua每个短字符串就浪费掉8字节) 长字符串用来标记字符串的长度(这里能表示8字节的长度,因为上面shrlen对于长字符串来说不够用),长字符串在lua中不是唯一的,所以不需要一个指针链起来 (hash64标准lua没有,无视) 实际字符串内容是拼接在这个字符串头之后,因此字符串的实际大小是24 字符串长度

Table

Lua的Table分为两部分,一个数组段和一个Map段

  • flags:一些标记位
  • lsizenode:Map的长度
  • sizearray:数组的长度
  • array:数组第一个元素的指针
  • node:Map第一个元素的指针
  • lastfree:Map段最后一个空位置的指针
  • metatable:这个Table的元表指针
  • gclist:这个Table内的托管对象

可以看到,一个空Table就至少要56字节的内存

Table中数组一个元素的结构:

Table中Map的一个KV元素的结构:

Table的实际大小,可以参考Lua垃圾回收时候遍历Table的代码:

Userdata

Proto

Proto就是Lua的函数原型,Lua函数的字节码都保存在这里,调用函数的地方只需要通过指向Proto的指针调用执行,具体结构很复杂就不细说了,可以看下图

内存占用:

闭包

分为C函数闭包和Lua闭包 C函数闭包:C的函数指针 UpValue数组 Lua闭包: Lua的函数原型指针 UpValue数组

UpValue结构如下:

内存占用:

Lua的局部变量(Proto里的描述)

最后

在需要统计lua详细占用内存的时候,可以遍历_G上的allgc对象列表,按上面规则逐一统计,这里简单贴一个UE4 Unlua的内存详细统计并打印到log中的控制台命令,整个统计方法就是根据上面实现的。

代码语言:javascript复制
struct FLuaGCObjectMemoryInfo
{
    // 字符串统计: <size, count>
    TMap<uint32, int32> ShortStringInfo;
    TMap<uint32, int32> LongStringInfo;
    TMap<uint32, int32> UserdataInfo;
    TMap<uint32, int32> LuaClosureInfo;
    TMap<uint32, int32> CFunctionInfo;
    TMap<uint32, int32> CClosureInfo;
    TMap<uint32, int32> TableInfo;
    TMap<uint32, int32> ThreadInfo;
    TMap<uint32, int32> ProtoInfo;
    
    TMap<uint32, int32> LuaClosureExtra;

    template <typename FmtEachType, typename FmtTotalType>
    static void SingleMapToOutputDevice(FOutputDevice& OutputDevice, TMap<uint32, int32>& Info,
                                        const FmtEachType& FmtEach, const FmtTotalType& FmtTotal)
    {
        int32 TotalSize = 0;
        int32 TotalCount = 0;
        for (auto& Pair : Info)
        {
            uint32 Size = Pair.Key;
            int32 Count = Pair.Value;
            TotalSize  = (Size * Count);
            TotalCount  = Count;
            OutputDevice.Logf(FmtEach, Size, Count);
        }
        OutputDevice.Logf(FmtTotal, TotalSize, TotalCount);
    }

    void ToOutputDevice(FOutputDevice& OutputDevice)
    {
        OutputDevice.Logf(TEXT("Lua Memory Detail Info Start:"));
        SingleMapToOutputDevice(OutputDevice, ShortStringInfo,
            TEXT("ShortString Each Size:%d Count:%d"),
            TEXT("ShortString Total Size:%d Count:%d"));
        SingleMapToOutputDevice(OutputDevice, LongStringInfo,
            TEXT("LongString Each Size:%d Count:%d"),
            TEXT("LongString Total Size:%d Count:%d"));
        SingleMapToOutputDevice(OutputDevice, UserdataInfo,
            TEXT("Userdata Each Size:%d Count:%d"),
            TEXT("Userdata Total Size:%d Count:%d"));
        SingleMapToOutputDevice(OutputDevice, LuaClosureInfo,
            TEXT("LuaClosure Each Size:%d Count:%d"),
            TEXT("LuaClosure Total Size:%d Count:%d"));
        SingleMapToOutputDevice(OutputDevice, CFunctionInfo,
            TEXT("CFuntion Each Size:%d Count:%d"),
            TEXT("CFuntion Total Size:%d Count:%d"));
        SingleMapToOutputDevice(OutputDevice, CClosureInfo,
            TEXT("CClosure Each Size:%d Count:%d"),
            TEXT("CClosure Total Size:%d Count:%d"));
        SingleMapToOutputDevice(OutputDevice, TableInfo,
            TEXT("Table Each Size:%d Count:%d"),
            TEXT("Table Total Size:%d Count:%d"));
        SingleMapToOutputDevice(OutputDevice, ThreadInfo,
            TEXT("Thread Each Size:%d Count:%d"),
            TEXT("Thread Total Size:%d Count:%d"));
        SingleMapToOutputDevice(OutputDevice, ProtoInfo,
            TEXT("Proto Each Size:%d Count:%d"),
            TEXT("Proto Total Size:%d Count:%d"));
        OutputDevice.Logf(TEXT("Lua Memory Detail Info End:"));
    }
    
    void Initialize()
    {
        lua_State* L = UnLua::GetMainState();
        if (L == nullptr)
        {
            return;
        }
        global_State* _G = G(L);
        lu_mem Count = MAX_LUMEM;
        const int OtherWhite = otherwhite(_G);
        for (GCObject* Iter = _G->allgc; Iter != nullptr && Count-- > 0; Iter = Iter->next)
        {
            GCObject* Obj = Iter;
            const int32 Marked = Obj->marked;
            if (isdeadm(OtherWhite, Marked))
            {
                continue;
            }
            switch (Obj->tt)
            {
            case LUA_TSHRSTR:
                {
                    uint32 Size = sizelstring(gco2ts(Obj)->shrlen);
                      ShortStringInfo.FindOrAdd(Size);
                break;
                }
            case LUA_TLNGSTR:
                {
#ifdef LUA_USE_LONG_STRING_CACHE
                    uint32 Size = sizelstring(gco2ts(Obj)->hash);
#else
                    uint32 Size = sizelstring(gco2ts(Obj)->u.lnglen);
#endif
                      LongStringInfo.FindOrAdd(Size);
                    break;
                }
            case LUA_TUSERDATA:
                {
                    uint32 Size = sizeudata(gco2u(Obj));
                      UserdataInfo.FindOrAdd(Size);
                    break;
                }
            case LUA_TLCL: 
                {
                    uint32 Size = sizeLclosure(gco2lcl(Obj)->nupvalues);
                      LuaClosureInfo.FindOrAdd(Size);
                    break;
                }
            case LUA_TCCL:
                {
                    uint32 Size = sizeCclosure(gco2ccl(Obj)->nupvalues);
                      CFunctionInfo.FindOrAdd(Size);
                    break;
                }
            case LUA_TLCF:
                {
                    uint32 Size = sizeLclosure(gco2lcl(Obj)->nupvalues);
                      CClosureInfo.FindOrAdd(Size);
                    break;
                }
            case LUA_TTABLE:
                {
                    Table* t = gco2t(Obj);
                    uint32 Size = sizeof(Table)   sizeof(TValue) * t->sizearray  
                        sizeof(Node) * cast(size_t, allocsizenode(t));
                      TableInfo.FindOrAdd(Size);
                    break;
                }
            case LUA_TTHREAD:
                {
                    lua_State* th = gco2th(Obj);
                    uint32 Size = (sizeof(lua_State)   sizeof(TValue) * th->stacksize  
                        sizeof(CallInfo) * th->nci);
                      ThreadInfo.FindOrAdd(Size);
                    break;
                }
            case LUA_TPROTO:
                {
                    Proto* f = gco2p(Obj);
                    uint32 Size = (f->sizecode) * sizeof(*(f->code));
                    Size  = (f->sizep) * sizeof(*(f->p));
                    Size  = (f->sizek) * sizeof(*(f->k));
                    Size  = (f->sizelineinfo) * sizeof(*(f->lineinfo));
                    Size  = (f->sizelocvars) * sizeof(*(f->locvars));
                    Size  = (f->sizeupvalues) * sizeof(*(f->upvalues));
                    Size  = sizeof(*(f));
                      ProtoInfo.FindOrAdd(Size);
                    break;
                }
            default:
                lua_assert(0);
            break;
            }
        }

        auto Lambda = [](uint32 L, uint32 R)
        {
          return L > R;
        };
        ShortStringInfo.KeySort(Lambda);
        LongStringInfo.KeySort(Lambda);
        UserdataInfo.KeySort(Lambda);
        LuaClosureInfo.KeySort(Lambda);
        CFunctionInfo.KeySort(Lambda);
        CClosureInfo.KeySort(Lambda);
        TableInfo.KeySort(Lambda);
        ThreadInfo.KeySort(Lambda);
        ProtoInfo.KeySort(Lambda);
    }
};

static FAutoConsoleCommandWithOutputDevice CCmdDumpMemory(
    TEXT("DumpMemory"),
    TEXT("DumpMemory"),
    FConsoleCommandWithOutputDeviceDelegate::CreateLambda([](FOutputDevice& OutputDevice)
    {
        FLuaGCObjectMemoryInfo Info;
        Info.Initialize();
        Info.ToOutputDevice(OutputDevice);
    }));

0 人点赞