由于lua是一个跨平台的脚本语言,会根据平台位数(16bit\32bit)、平台类型(Linux\Windows)、语言标准(C89\C99)、以及编译参数等开启预编译选项,导致基本数据结构的字长和类型会动态变化,以linux_ x86_64 进行编译为基础进行分析介绍,lua版本5.3.4。并根据我们开发过程中一些常见的情景进行分析:
1、基础数据结构Lua的基本数据表示方式是type + union的方式,根据不同类型映射到union的不同结构上面, 统一的表示结构lua_TValue:
typedef union Value {
GCObject *gc; /* collectable objects */
void *p; /* light userdata */
int b; /* booleans */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
} Value;
struct lua_TValue {
Value value_;
int tt_;
} TValue;
但由于lua_Integer和lua_Number在当前平台预处理后,分别定义成long long 和 double类型,所以为方便理解,上述结构经过转义:
typedef union Value {
GCObject *gc; /* collectable objects */
void *p; /* light userdata */
int b; /* booleans */
lua_CFunction f; /* light C functions */
long long i; /* integer numbers */
double n; /* float numbers */
} Value;
struct lua_TValue {
Value value_;
int tt_;
} TValue;
在lua5.1版本中,统一使用lua_Number来表示整数和浮点数,而double能够表示的整数大小有限,大概2^52的长度,所以用lua_Number表示一些类型为int64_t的全局唯一id长度不够,类似物品id、角色id。在lua5.3中,上述问题不再存在。
lua简单数据类型bool、整型、浮点型都是统一用lua_TValue来表示,消耗内存为sizeof(lua_TValue) = 16字节。因此相对C\C++表示整型的char\short\int\int64_t来说,这种数据结构的表示法虽然比较统一,但比较消耗内存。还有一些复杂的数据结构,统一封装在GCObject中。由于5.1和5.3表示法略有差异,5.1方便理解,如下:
union GCObject {
GCheader gch;
union TString ts;
union Udata u;
union Closure cl;
struct Table h;
struct Proto p;
struct UpVal uv;
struct lua_State th; /* thread */
};
在开发过程中,最常用的数据结构是sting和table类型,那么现在主要分析这两种数据结构的内存占用。
2.1 string 类型String又细分为短字符串LUA_TSHRSTR和长字符串LUA_TLNGSTR两种,默认长度小于40的为LUA_TSHRSTR,使用全局stringtable进行管理。即所有短字符串都在stringtable中存放,相同字符串只会有一份实际数据拷贝,每份相同的TString对象只是存放一个hash值,用来索引stringtable。而长字符串则跟普通的GCObject没有差别,相同字符串在内存都是单独一份数据拷贝。在Lua5.1中,没有区分长短字符串,所有的字符串统一在stringtable中存在唯一拷贝。猜想这种改变一是因为长字符串出现相同的情况比较少,二是lua5.1的方式长字符串TString计算Hash是抽取部分字符进行运算,这样的计算方式可能被伪造导致不同字符串的hash值一样,但要是所有字符全用来计算hash又比较耗时。
下面是一个string类型的GCObject的内存表示,根据长短字符串表示方式不一样:
2.1.1 内存占用分析实践