0

这个问题可能是针对 Lua 和 tolua 专家的。

我正在使用 tolua++1.0.93 和 lua-5.1.4(CEGUI 0.84 依赖项)。我已经跟踪这个讨厌的内存泄漏几个小时了,我发现 toluapp 在 Lua 注册表中创建了 tolua_gc 表,并且该表似乎无限增长。

当我使用 tolua_pushusertype_and_takeownership 将我的对象推送到 Lua 时,我希望 Lua 的 GC 删除我的对象。它确实这样做了,但是 tolua_pushusertype_and_takeownership 调用 tolua_register_gc ,它把这个对象在 object 作为这个“全局” tolua_gc 表的键。当 tolua_gc_event 函数调用收集器函数(调用删除运算符)时,它将 nil 值设置为刚刚删除的对象下的 tolua_gc 表作为键。所以这应该有效,对吧?

嗯,不。

也许我理解错了,但这似乎对 tolua_gc 表的大小没有影响。我也尝试过从 Lua 手动调用 tolua.releaseownership(object) 。它奏效了。我的意思是,它减少了 Lua (LUA_GCCOUNT) 使用的内存,但由于它断开了收集器与对象的连接,因此永远不会调用 operator delete 并且它在 C++ 中造成内存泄漏。

这真的很奇怪,因为 tolua.releaseownership 所做的只是将 nil 值设置为传递对象下的 tolua_gc 表作为键。那么为什么 tolua.releaseownership 会减少 Lua 使用的内存大小,而 tolua_gc_event 不会呢?唯一的区别是 tolua.releaseownership 在将 nil 设置为 tolua_gc 表之前调用垃圾收集器,而 tolua_gc_event 由垃圾收集器调用(相反情况)。

为什么我们需要那个全局 tolua_gc 表?我们不能在收集的时候直接从对象中获取元表吗?

我可以从这个进程中使用的内存真的很有限(8MB),而且似乎这个 tolua_gc 表在一段时间后占据了它的 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.release 所有权:

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
4

1 回答 1

0

问题不是错误或内存泄漏。尽管如果您的内存确实有限,您可以说这是内存泄漏。问题是 tolua_gc 是 lua 表,当您通过将元素设置为 nil 来删除元素时,它不会重新散列。

虽然我认为这可能是问题所在,但我很愚蠢,看不到它是否属实。因此垃圾收集器不能强制表重新散列并缩小其大小。所以表会增长,直到某些插入会触发重新散列。阅读:http ://www.lua.org/gems/sample.pdf

所以最后我删除了 tolua_gc 表,并将元表(tolua 曾经以 lightuserdata 作为键放入 tolua_gc 表)作为 userdata 对象本身的特殊字段。而且,我现在不是从 tolua_gc 表中访问这些元表,而是从对象本身获取它。其他一切都是一样的,它似乎工作。

于 2016-02-29T12:19:42.220 回答