使用 tolua_pushusertype_and_takeownership 时 toluapp 是否会造成内存泄漏?
Does toluapp create memory leak when tolua_pushusertype_and_takeownership is used?
这个问题可能是给 Lua 和 lua 专家的。
我正在使用 tolua++1.0.93 和 lua-5.1.4(CEGUI 0.84 依赖项)。
我一直在跟踪这个令人讨厌的内存泄漏几个小时,我发现 toluapp 在 Lua 注册表中创建 tolua_gc table 并且似乎这个 table无限增长。
当我使用 tolua_pushusertype_and_takeownership 将我的对象推送到 Lua 时,我希望 Lua 的 GC 删除我的对象。它确实这样做了,但是 tolua_pushusertype_and_takeownership 调用 tolua_register_gc 将这个对象元 table 放在对象下面作为这个 "global" tolua_gc table 的键。
当 tolua_gc_event 函数调用收集器函数(调用 delete 运算符)时,它将 nil 值设置为刚刚删除的对象下的 tolua_gc table 作为键。这样应该可以,对吧?
嗯,没有。
也许我理解有误,但这似乎对 tolua_gc table 的大小没有影响。
我还尝试从 Lua 手动调用 tolua.releaseownership(object)。它奏效了。我的意思是,它减少了 Lua (LUA_GCCOUNT) 使用的内存,但由于它断开了收集器与对象的连接,因此永远不会调用运算符 delete 并在 C++ 中造成内存泄漏。
这真的很奇怪,因为所有 tolua.releaseownership 所做的就是将传递的对象下的 nil 值设置为 tolua_gc table 作为键。
那么,为什么 tolua.releaseownership 减少了 Lua 使用的内存大小,而 tolua_gc_event 却没有?
唯一的区别是tolua.releaseownership在设置nil为tolua_gc之前调用垃圾收集器table,而tolua_gc_event被垃圾收集器调用(相反的情况)。
为什么我们需要全局 tolua_gc table?我们不能在收集时直接从对象中获取 metatable 吗?
我可以从这个进程中使用的内存非常有限 (8MB),一段时间后这个 tolua_gc table 似乎占据了它的 90%。
我该如何解决这个问题?
谢谢。
编辑:
这些是代码示例:
extern unsigned int TestSSCount;
class TestSS
{
public:
double d_double;
TestSS()
{
// TestSSCount++;
// fprintf(stderr, "c(%d)\n",TestSSCount);
}
TestSS(const TestSS& other)
{
d_double = other.d_double * 0.5;
// TestSSCount++;
// fprintf(stderr, "cc(%d)\n",TestSSCount);
}
~TestSS()
{
// TestSSCount--;
// fprintf(stderr, "d(%d)\n", TestSSCount);
}
};
class App
{
...
TestSS doNothing()
{
TestSS t;
t.d_double = 13.89;
return t;
}
void callGC()
{
int kbs_before = lua_gc(d_state, LUA_GCCOUNT, 0);
lua_gc(d_state, LUA_GCCOLLECT, 0);
int kbs_after = lua_gc(d_state, LUA_GCCOUNT, 0);
printf("GC changed memory usage from %d kB to %d kB, difference %d kB",
kbs_before, kbs_after, kbs_before - kbs_after);
}
...
};
这是 .pkg 文件:
class TestSS
{
public:
double d_double;
};
class App
{
TestSS doNothing();
void callGC();
};
现在完成 Lua 代码(app 和 rootWindow 是作为常量提供给 Lua 的 C++ 对象):
function handleCharacterKey(e_)
local key = CEGUI.toKeyEventArgs(e_).scancode
if key == CEGUI.Key.One then
for i = 1,10000,1 do
-- this makes GC clear all memory from Lua heap but does not call destructor in C++
-- tolua.releaseownership(app:doNothing())
-- this makes GC call destructors in C++ but somehow makes Lua heap increase constantly
app:doNothing()
elseif key == CEGUI.Key.Zero then
app:callGC()
end
end
rootWindow:subscribeEvent("KeyUp", "handleCharacterKey")
这是我按下 0 1 0 1 0 1 0 时得到的输出:
这是我用tolua.releaseowenership
的时候
GC changed memory usage from 294 kB to 228 kB, difference 66 k
GC changed memory usage from 228 kB to 228 kB, difference 0 kB
GC changed memory usage from 228 kB to 228 kB, difference 0 kB
GC changed memory usage from 228 kB to 228 kB, difference 0 kB
这个没有tolua.releaseownership:
GC changed memory usage from 294 kB to 228 kB, difference 66 kB
GC changed memory usage from 605 kB to 604 kB, difference 1 kB
GC changed memory usage from 982 kB to 861 kB, difference 121 kB
GC changed memory usage from 1142 kB to 1141 kB, difference 1 kB
这没有释放所有权,但我在键盘上按下的序列是 0 1 0 1 0 1 0 0 0 0(最后再额外调用 GC 三个)
GC changed memory usage from 294 kB to 228 kB, difference 66 kB
GC changed memory usage from 603 kB to 602 kB, difference 1 kB
GC changed memory usage from 982 kB to 871 kB, difference 111 kB
GC changed memory usage from 1142 kB to 1141 kB, difference 1 kB
GC changed memory usage from 1141 kB to 868 kB, difference 273 kB <- this is after first additional GC call
GC changed memory usage from 868 kB to 868 kB, difference 0 kB
GC changed memory usage from 868 kB to 868 kB, difference 0 kB
问题不是错误或内存泄漏。尽管如果您的内存确实有限,您可以说这是内存泄漏。问题是 tolua_gc 是 lua table,当你通过将它们设置为 nil 来删除元素时不会重新散列。
虽然我认为这可能是问题所在,但我太愚蠢了,没有看到它是否属实。所以垃圾收集器不能强制 table 重新散列和缩小它的大小。所以 table 会增长,直到某些插入会触发重新散列。
读这个:http://www.lua.org/gems/sample.pdf
所以最后我删除了 tolua_gc table 并放入了 metatables(tolua 曾经放在 tolua_gc table 以 lightuserdata 作为键)作为 userdata 对象本身的特殊字段。
而且我现在不是从 tolua_gc table 访问那些元 table,而是从对象本身获取它。其他都是一样的,好像也行。
这个问题可能是给 Lua 和 lua 专家的。
我正在使用 tolua++1.0.93 和 lua-5.1.4(CEGUI 0.84 依赖项)。 我一直在跟踪这个令人讨厌的内存泄漏几个小时,我发现 toluapp 在 Lua 注册表中创建 tolua_gc table 并且似乎这个 table无限增长。
当我使用 tolua_pushusertype_and_takeownership 将我的对象推送到 Lua 时,我希望 Lua 的 GC 删除我的对象。它确实这样做了,但是 tolua_pushusertype_and_takeownership 调用 tolua_register_gc 将这个对象元 table 放在对象下面作为这个 "global" tolua_gc table 的键。 当 tolua_gc_event 函数调用收集器函数(调用 delete 运算符)时,它将 nil 值设置为刚刚删除的对象下的 tolua_gc table 作为键。这样应该可以,对吧?
嗯,没有。
也许我理解有误,但这似乎对 tolua_gc table 的大小没有影响。 我还尝试从 Lua 手动调用 tolua.releaseownership(object)。它奏效了。我的意思是,它减少了 Lua (LUA_GCCOUNT) 使用的内存,但由于它断开了收集器与对象的连接,因此永远不会调用运算符 delete 并在 C++ 中造成内存泄漏。
这真的很奇怪,因为所有 tolua.releaseownership 所做的就是将传递的对象下的 nil 值设置为 tolua_gc table 作为键。 那么,为什么 tolua.releaseownership 减少了 Lua 使用的内存大小,而 tolua_gc_event 却没有? 唯一的区别是tolua.releaseownership在设置nil为tolua_gc之前调用垃圾收集器table,而tolua_gc_event被垃圾收集器调用(相反的情况)。
为什么我们需要全局 tolua_gc table?我们不能在收集时直接从对象中获取 metatable 吗?
我可以从这个进程中使用的内存非常有限 (8MB),一段时间后这个 tolua_gc table 似乎占据了它的 90%。
我该如何解决这个问题?
谢谢。
编辑: 这些是代码示例:
extern unsigned int TestSSCount;
class TestSS
{
public:
double d_double;
TestSS()
{
// TestSSCount++;
// fprintf(stderr, "c(%d)\n",TestSSCount);
}
TestSS(const TestSS& other)
{
d_double = other.d_double * 0.5;
// TestSSCount++;
// fprintf(stderr, "cc(%d)\n",TestSSCount);
}
~TestSS()
{
// TestSSCount--;
// fprintf(stderr, "d(%d)\n", TestSSCount);
}
};
class App
{
...
TestSS doNothing()
{
TestSS t;
t.d_double = 13.89;
return t;
}
void callGC()
{
int kbs_before = lua_gc(d_state, LUA_GCCOUNT, 0);
lua_gc(d_state, LUA_GCCOLLECT, 0);
int kbs_after = lua_gc(d_state, LUA_GCCOUNT, 0);
printf("GC changed memory usage from %d kB to %d kB, difference %d kB",
kbs_before, kbs_after, kbs_before - kbs_after);
}
...
};
这是 .pkg 文件:
class TestSS
{
public:
double d_double;
};
class App
{
TestSS doNothing();
void callGC();
};
现在完成 Lua 代码(app 和 rootWindow 是作为常量提供给 Lua 的 C++ 对象):
function handleCharacterKey(e_)
local key = CEGUI.toKeyEventArgs(e_).scancode
if key == CEGUI.Key.One then
for i = 1,10000,1 do
-- this makes GC clear all memory from Lua heap but does not call destructor in C++
-- tolua.releaseownership(app:doNothing())
-- this makes GC call destructors in C++ but somehow makes Lua heap increase constantly
app:doNothing()
elseif key == CEGUI.Key.Zero then
app:callGC()
end
end
rootWindow:subscribeEvent("KeyUp", "handleCharacterKey")
这是我按下 0 1 0 1 0 1 0 时得到的输出:
这是我用tolua.releaseowenership
的时候GC changed memory usage from 294 kB to 228 kB, difference 66 k
GC changed memory usage from 228 kB to 228 kB, difference 0 kB
GC changed memory usage from 228 kB to 228 kB, difference 0 kB
GC changed memory usage from 228 kB to 228 kB, difference 0 kB
这个没有tolua.releaseownership:
GC changed memory usage from 294 kB to 228 kB, difference 66 kB
GC changed memory usage from 605 kB to 604 kB, difference 1 kB
GC changed memory usage from 982 kB to 861 kB, difference 121 kB
GC changed memory usage from 1142 kB to 1141 kB, difference 1 kB
这没有释放所有权,但我在键盘上按下的序列是 0 1 0 1 0 1 0 0 0 0(最后再额外调用 GC 三个)
GC changed memory usage from 294 kB to 228 kB, difference 66 kB
GC changed memory usage from 603 kB to 602 kB, difference 1 kB
GC changed memory usage from 982 kB to 871 kB, difference 111 kB
GC changed memory usage from 1142 kB to 1141 kB, difference 1 kB
GC changed memory usage from 1141 kB to 868 kB, difference 273 kB <- this is after first additional GC call
GC changed memory usage from 868 kB to 868 kB, difference 0 kB
GC changed memory usage from 868 kB to 868 kB, difference 0 kB
问题不是错误或内存泄漏。尽管如果您的内存确实有限,您可以说这是内存泄漏。问题是 tolua_gc 是 lua table,当你通过将它们设置为 nil 来删除元素时不会重新散列。
虽然我认为这可能是问题所在,但我太愚蠢了,没有看到它是否属实。所以垃圾收集器不能强制 table 重新散列和缩小它的大小。所以 table 会增长,直到某些插入会触发重新散列。 读这个:http://www.lua.org/gems/sample.pdf
所以最后我删除了 tolua_gc table 并放入了 metatables(tolua 曾经放在 tolua_gc table 以 lightuserdata 作为键)作为 userdata 对象本身的特殊字段。 而且我现在不是从 tolua_gc table 访问那些元 table,而是从对象本身获取它。其他都是一样的,好像也行。