理论计算出来的内存占用比实际消耗多了1724403-1724344 = 59字节,似乎两个字符串的消耗没有计算进来。那么这两个字符串”score” ”rank” 是否真正存在于stringTable了? 答案是yes。只是在更早阶段,lua程序对代码文件进行词法分析,生成指令码的过程中,就已经调用luaS_newlstr创建了”score”和”rank”字符串了,因此不在上述代码的统计范围内,有兴趣的可以调试lua程序验证。
场景二:
去掉每条子表记录里面的key,直接按有序列表存储。
collectgarbage("stop");
before = collectgarbage("count");
tab = {}
for i = 1, 10000 do
tab[100000+i] = {100000+i, i}
end
after = collectgarbage("count");
print("total mem:", (after - before)*1024)
total mem: 1404344.0
A,每个小表{100000+i, i}, sizeof(Table) + sizeof(TValue) * 2 = 88。
B,大表有1W条记录,2^14 = 16384 > 10000。sizeof(Table) + sizeof(Node) * 16384 = 524344
C,最终内存占用:88* 10000 + 524344 = 1404344
场景三:
如果列表只有2个变量,可以把两个数值合并,减少一层表的分配,那么就会减少场景二A子表的内存消耗。
collectgarbage("stop");
before = collectgarbage("count");
tab = {}
for i = 1, 10000 do
tab[100000+i] = (100000 + i) << 14 + i
end
after = collectgarbage("count");
print("total mem:", (after - before)*1024)
total mem: 524344.0
由于value只是普通的数值,那么Node节点的TValue足够存储,不在单独分配额外内存,所以内存大小:sizeof(Table) + sizeof(Node) * 16384 = 524344
场景四:
如果还要继续压缩内存使用,可以考虑不要存1W条记录,存8192条记录如果也满足要求的话,这样可以减少一次hashtable的预分配。
collectgarbage("stop");
before = collectgarbage("count");
tab = {}
for i = 1, 8192 do
tab[100000+i] = (100000 + i) << 14 + i
end
after = collectgarbage("count");
print("total mem:", (after - before)*1024)
total mem: 262200.0
A、大表的hashtable记录数2^13 = 8192, 那么刚好把节点使用完,没有浪费。
B、总体内存:sizeof(Table) + sizeof(Node) * 8192= 262200
所以,如果对lua table的动态内存管理比较清楚,可以在满足需求的情况进行一些优化,能够大幅压缩内存的占用,如上可以看到内存从最早的1724344 优化到262200, 大概只有原来内存占用的1/7。当然了,也不能只考虑内存的占用,也要结合扩展性综合考虑,比如场景四里面由于少了一层表,会导致单条记录没法扩展字段。因此,不论是节省内存还是兼顾扩展性,是综合评估的结果,只有各方面利弊能够准确评估,才能做很好的tradeofff。
场景五:
对比C/C++的实现
struct rankitem
{
Uint64_t uid;
Int score;
Short rank_no;
};
struct ranklist
{
short num;
struct rankitem list[8192];
};