Создает ли toluapp утечку памяти при использовании tolua_pushusertype_and_takeownership?

Этот вопрос может быть для экспертов Lua и Tolua.

Я использую tolua++1.0.93 и lua-5.1.4 (зависимости CEGUI 0.84). Я отслеживал эту неприятную утечку памяти в течение нескольких часов и обнаружил, что toluapp создает таблицу tolua_gc в реестре Lua, и кажется, что эта таблица растет бесконечно.

Когда я перемещаю свой объект в Lua с помощью tolua_pushusertype_and_takeownership, я хочу, чтобы Lua GC удалил мой объект. И это так, но tolua_pushusertype_and_takeownership вызывает tolua_register_gc, который помещает эти объекты метатабильно под объект в качестве ключа к этой "глобальной" таблице tolua_gc. Когда функция tolua_gc_event вызывает функцию сборщика (которая вызывает оператор удаления), она устанавливает значение nil в таблицу tolua_gc для только что удаленного объекта в качестве ключа. Так что должно работать, верно?

Ну нет.

Может быть, я что-то не так понял, но, похоже, это никак не влияет на размер таблицы tolua_gc. Я также пытался вручную вызвать tolua.releaseownership (объект) из Lua. И это сработало. Я имею в виду, что он уменьшил объем памяти, используемой Lua (LUA_GCCOUNT), но, поскольку он отключил сборщик от объекта, оператор delete никогда не вызывался, и это вызывало утечки памяти в C++.

Это действительно странное поведение, потому что все, что делает tolua.releaseownership, - это устанавливает значение nil в таблицу tolua_gc под переданным объектом в качестве ключа. Так почему же tolua.releaseownership уменьшает размер памяти, используемой Lua, а tolua_gc_event нет? Единственное отличие состоит в том, что tolua.releaseownership вызывает сборщик мусора до того, как он установит nil в таблицу tolua_gc, а сборщик мусора вызывает tolua_gc_event (противоположная ситуация).

Зачем нам нужна эта глобальная таблица tolua_gc? Разве мы не можем просто взять метатабельный объект непосредственно в момент сбора?

У меня действительно ограниченная память, которую я могу использовать из этого процесса (8 МБ), и кажется, что эта таблица 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 являются объектами C++, предоставленными как константы Lua):

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

Это без толуа.

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

1 ответ

Решение

Проблема не в ошибке или утечке памяти. Хотя вы могли бы сказать, что это утечка памяти, если у вас действительно ограниченная память. Проблема в том, что tolua_gc, будучи таблицей lua, не перефразирует, когда вы удаляете элементы, устанавливая их в ноль.

Хотя я предполагал, что это может быть проблемой, я был достаточно глуп, чтобы не видеть, правда ли это. Поэтому сборщик мусора не может заставить таблицу перефразировать и уменьшить ее размер. Таким образом, таблица будет расти до определенной вставки, которая вызовет перефразировку. Прочитайте это: http://www.lua.org/gems/sample.pdf

Итак, в конце я удалил таблицу tolua_gc и поместил метатаблицы (которые tolua использовал для помещения в таблицу tolua_gc с ключом lightuserdata) в качестве специального поля в самом объекте userdata. А также вместо того, чтобы обращаться к этим метатаблицам из таблицы tolua_gc, я получаю их сейчас из самого объекта. Все остальное тоже самое, и похоже на работу.

Другие вопросы по тегам