Lua问题:创建包含大量数据的table后再删除其中的key,table占用的内存没有释放

814 views
Skip to first unread message

YuLei Liao

unread,
Oct 6, 2013, 1:36:05 AM10/6/13
to open...@googlegroups.com
假设创建一个包含10万个字符串key的table,然后将这些key对应的值设置为nil。

可table占用的内存并没有释放。请问这种情况应该如何解决呢?谢谢大家

Yichun Zhang (agentzh)

unread,
Oct 6, 2013, 1:47:14 AM10/6/13
to openresty
Hello!

2013/10/5 YuLei Liao:
> 假设创建一个包含10万个字符串key的table,然后将这些key对应的值设置为nil。
>
> 可table占用的内存并没有释放。请问这种情况应该如何解决呢?谢谢大家
>

将 key 对应的值置为 nil 并不会让 Lua table 回改空间;这是期望的行为。如果你想释放空间,应当让整个 Lua table 被垃圾回收。

下面这篇题为 "Lua Performance Tips" 的 PDF 文件对此问题以及解决办法有非常详尽的阐述:

http://www.lua.org/gems/sample.pdf

Regards,
-agentzh

廖宇雷

unread,
Oct 7, 2013, 10:22:47 PM10/7/13
to open...@googlegroups.com
非常感谢 :)

在 2013年10月6日星期日UTC+8下午1时47分14秒,agentzh写道:

廖宇雷

unread,
Oct 10, 2013, 10:29:18 AM10/10/13
to open...@googlegroups.com

我做了一些测试,发现强制 rehash 也不能完全回收 table 占用的空间。

local a = {}
local limit = 100000
for i = 1, limit do
    a
[tostring(i)] = i
end

print(string.format("mem %0.2fKB", collectgarbage("count")))
print("----------------------------------------")

-- erase all its elements
for k, _ in pairs(a) do
    a
[k] = nil
end

-- trick to force a rehash
for i = limit + 1, limit * 2 do
    a
[i] = nil
end

collectgarbage
("collect")

print(string.format("mem %0.2fKB", collectgarbage("count")))

luajit 的执行结果如下:

mem 5748.63KB
----------------------------------------
mem
3355.46KB

如果把 a[tostring(i)] = i 改为 a[i] = i,那么结果会有很大不同,最终内存占用只有不到 20KB。
因此,Lua 在处理 hash table 时,应该采用了和 index array 不同的策略。这导致 table 占用的大量内存无法被回收。
要完全回收内存,只能是 a = {} 重新赋值一个新的空 table,并且做一次强制垃圾回收。

我之所以纠结此问题,是因为 cocos2d-x 采用 tolua++ 实现 luabinding。而 tolua++ 用 table 来保存 C++ 对象的指针。如果创建大量对象,那么这些 table 就会越来越大,最后导致内存不足。例如在 iPod Touch 4 上,OpenGL 材质可以使用 70-80MB 内存,应用程序只有 10M-20M 内存可用。由于游戏运行过程中,会不断创建对象,最终 Lua table 会消耗光所有内存。

在 2013年10月6日星期日UTC+8下午1时47分14秒,agentzh写道:

Yichun Zhang (agentzh)

unread,
Oct 10, 2013, 4:12:09 PM10/10/13
to openresty
Hello!

2013/10/10 廖宇雷
>
> 我做了一些测试,发现强制 rehash 也不能完全回收 table 占用的空间。
>

你的测试程序本身是有问题的。因为你在初始化 table a 的时候,key 是字符串类型,于是你分配空间的是 table 的 hash
表部分。而你后面强制 rehash 的时候使用的是数值类型的 key,于是只会作用于数组部分的。

> local a = {}
> local limit = 100000
> for i = 1, limit do
> a[tostring(i)] = i

注意你这里使用了 tostring() 进行了 key 的类型转换。去掉这里的 tostring() 调用,便可以实现 rehash
空间释放了。在我这里使用 luajit 运行修改后的版本的输出是

mem 1052.57KB
----------------------------------------
mem 29.64KB

还有一种更简单的做法是直接丢弃原来的 table,让其整体 GC:

local a = {}
local limit = 100000
for i = 1, limit do
a[tostring(i)] = i
end

collectgarbage("collect")
print(string.format("mem %0.2fKB", collectgarbage("count")))
print("----------------------------------------")

a = {}

collectgarbage("collect")
print(string.format("mem %0.2fKB", collectgarbage("count")))

在我这里的输出结果是

mem 5748.67KB
----------------------------------------
mem 283.08KB

Regards,
-agentzh
Reply all
Reply to author
Forward
0 new messages