Lua中每个数据类型都是一个TValue
可以看到基本类型(浮点数,整数,布尔值,lightuserdata,C++函数)至少会占用 12字节 (内存对齐后16字节)
gc这个指针指Lua虚拟机托管的对象包括字符串,Table,Userdata,协程,闭包,Proto等,内存由虚拟机额外分配并托管,下面具体说
每个GC对象都有个公有的头,next表示全局gc池的下一个节点的指针,将所有的gc对象都链起来 (PS:对比ue4是使用一个全局Object数组实现的,Lua每个节点就浪费掉8字节) tt是当前对象的类型,和上面的tt_是一样的 marked是给垃圾回收器用的标记位
因此,GC对象至少会占用10字节的头部内存
Lua的Table分为两部分,一个数组段和一个Map段
可以看到,一个空Table就至少要56字节的内存
Table中数组一个元素的结构:
Table中Map的一个KV元素的结构:
Table的实际大小,可以参考Lua垃圾回收时候遍历Table的代码:
Proto就是Lua的函数原型,Lua函数的字节码都保存在这里,调用函数的地方只需要通过指向Proto的指针调用执行,具体结构很复杂就不细说了,可以看下图
内存占用:
分为C函数闭包和Lua闭包 C函数闭包:C的函数指针+UpValue数组 Lua闭包: Lua的函数原型指针+UpValue数组
UpValue结构如下:
内存占用:
在需要统计lua详细占用内存的时候,可以遍历_G上的allgc对象列表,按上面规则逐一统计,这里简单贴一个UE4+Unlua的内存详细统计并打印到log中的控制台命令,整个统计方法就是根据上面实现的。
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);
}));