基本类型
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);
}));