2016-11-07 5 views
2

Я обнаружил, что у меня плохая утечка памяти в проекте C#/Lua LuaInterface. Я написал простую тестовую функцию в C#, которая вызывается из Lua в цикле каждые 0,5 секунды. Я вижу, что использование Lua memmory увеличивается с каждым циклом. Мой последний C# сторона кодОшибка утечки памяти LuaInterface

public LuaTable testMemTable() 
    { 
    LuaTable tabx = m_lua.GetTable("tabx"); 

    if (tabx != null) 
    { 
     tabx.Dispose(); 
     tabx = null; 

     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
    } 


    m_lua.NewTable("tabx"); 
    tabx = m_lua.GetTable("tabx"); 

    for (int i = 0; i < 20000; i++) 
     tabx[i] = i * 10; 

    return tabx; 
    } 

несмотря на выполнение tabx.Dispose() и tabx = нуль, а затем принуждать GC Я до сих пор, видя, что память не освобождается. Нет функции LuaInterface, чтобы освободить ранее выделенную таблицу, поэтому я теряю то, что еще могу сделать, чтобы освободить память?

Lua код на стороне очень просто

while true do 

    myAPILibs.testMemTable() 

    if tabx ~= nil then 
     print(string.format("Size = %d", #tabx)) 
    else 
     print(string.format("Size = nil")) 
    end 

    print(tabx, tabx[111]) 

    myAPILibs.sleep(500) 

    print("Before ", collectgarbage("count") * 1024) 
    collectgarbage("collect") 
    print("After ", collectgarbage("count") * 1024) 

end 

Любая помощь решает моя проблема утечки памяти было бы весьма признателен.

Еще раз спасибо

Geoff

ответ

1

мне нужно разглагольствовать о LuaInterface немного, потому что это делает эту проблему очень трудно решить - в некоторых случаях это даже невозможно не утечка памяти.

Прокси сайтов CLR в LuaInterface (и NLua) не предоставляют финализаторы, которые освобождают ссылку Lua. Вы должны располагать каждый объект CLR, который ссылается на объект Lua. Если вы забудете один раз, объект Lua никогда не сможет быть собранным мусором.

Хуже того, вы не можете правильно распорядиться ссылкой, которую вы возвращаете из метода CLR, который вызывается Lua. Если вы избавитесь от ссылки, прежде чем вернуть ее, она исчезнет, ​​и вы больше не сможете ее вернуть. Если вы его не уничтожаете, ссылка CLR просочилась. Есть программная гимнастика, которую вы можете сделать, чтобы правильно распоряжаться ссылкой, но нет никаких оснований полагать, что эта работа должна быть необходима с вашей стороны.

Теперь позвольте мне сойти с моего soapbox и обратиться к вашему коду.

Есть много мест во всех ваших методах, где вы просачиваете эти объекты. Ваше использование GetTable() опровергает ваше непонимание того, как это работает. Каждый раз, когда вы вызываете этот метод, вы получаете новый объект CL12, который ссылается на таблицу Lua, на которую ссылается эта глобальная переменная Lua. Ваше использование этого, кажется, предполагает, что вы можете сделать m_lua.GetTable("tabx").Dispose() и выполнить что-то - все это делает, это создать новую ссылку CLR, а затем утилизировать только одну ссылку. Другими словами, это дорогой способ вообще ничего не делать.

Каждый вызов GetTable() должен иметь соответствующее распоряжение (или использовать using блок C# 's, чтобы сделать удаление для вас).

Для уточнения, распоряжения CLR LuaTable объект не уничтожить стол Lua! Все, что он делает, - это выпуск , что конкретно CLR ссылается на таблицу.

Это справедливо для каждый Тип LuaInterface, который ссылается на объект Lua, включая функции, который является другим местом, где вы потенциально протекаете.

Здесь я покажу проблемы с каждым методом и как вы перепишете его для решения этих проблем.

public LuaTable testMemTable() 
{ 
    // This code does nothing. You get a new reference to the Lua table 
    // object, and then you immediately dispose it. You can remove this 
    // whole chunk of code; it's a really expensive no-op. 
    LuaTable tabx = m_lua.GetTable("tabx"); 

    if (tabx != null) 
    { 
     tabx.Dispose(); 
     tabx = null; 

     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
    } 


    m_lua.NewTable("tabx"); 

    // You create a new CLR object referencing the new Lua table, but you 
    // don't dispose this CLR object. 
    tabx = m_lua.GetTable("tabx"); 

    for (int i = 0; i < 20000; i++) 
     tabx[i] = i * 10; 

    return tabx; 
} 

Правильный способ написать этот метод будет:

public void testMemTable() 
{ 
    m_lua.NewTable("tabx"); 

    using (LuaTable tabx = m_lua.GetTable("tabx")) { 
     for (int i = 0; i < 20000; i++) { 
      tabx[i] = i * 10; 
     } 
    } 
} 

(Обратите внимание, что я изменил тип возврата к мочеиспусканию, так как вы никогда не использовать возвращаемое значение.)

public LuaTable getNewTableCSharp() 
{ 
    // You don't dispose the function object. 
    var x = lua.GetFunction("getNewTableLua"); 
    // You don't dispose the table object. 
    var retValTab = (LuaTable)x.Call()[0]; 

    return retValTab; 
} 

Обратите внимание: поскольку функция getNewTableLua никогда не переназначается, вы фактически не протекаете функцию Lua, но при каждом вызове этой функции вы получаете : просачивается слот в таблице Lua, который содержит ссылку на эту функцию.

Теперь вот загвоздка: так как эта функция вызывается из Lua и возвращает ссылку на объект Lua, вы не можете исправить обе утечки, вы можете только исправить функцию утечки:

public LuaTable getNewTableCSharp() 
{ 
    using (var x = lua.GetFunction("getNewTableLua")) { 
     // Still leaks, but you can't do anything about it. 
     return (LuaTable)x.Call()[0]; 
    } 
} 

Чтобы получить вернемся на мой soapbox, вместо этого используйте вместо этого Eluant, который представляет собой набор привязок Lua для CLR (как LuaInterface), за исключением того, что он решает проблемы управления памятью. (Отказ от ответственности:. Я являюсь автором элюанта)

В частности, он решает проблемы, вы столкнулись здесь:

  • объекты CLR элюанта, что ссылки на объекты Lua имеют финализаторы, которые будут стоять в очереди ссылку Lua на выпустить позже. Если вы забудете удалить объект CLR, ссылающийся на объект Lua, в конечном итоге он будет собран. (Но вы все равно должны распоряжаться ссылками как можно скорее, предпочтительно используя блоки C# using, чтобы убедиться, что GC Lua может собирать объекты своевременно.)
  • Если вы вернете ссылку объекта CLR объекту Lua в методе, который был вызывается Lua, Eluant будет использовать ссылку для вас, прежде чем он вернет управление Lua.

See here. Финализатор присутствует, но утилита не освобождает ссылку на объект Lua, если она была вызвана финализатором. Другими словами, финализатор в принципе ничего не делает. Обратите внимание, что поскольку Lua не является потокобезопасным, на данный момент небезопасно выпускать ссылку Lua, но операция выпуска может быть поставлена ​​в очередь на последующие. LuaInterface этого не делает; Eluant does.

1

Короткий ответ

Заменить lua.newTable("testTable") с
lua.doString("for key,value in ipairs(testTable) do testTable[key]=nil end");

Там не будет никакой утечки памяти и ваш код работает отлично.

Длинный ответ

Позволяет не сказать, что вы были Lua скрипт так:

newTable = {1,2,3,4,5,6,7,8,9,10,11,12,13,14} 
newTable = {} 

Если вы сделаете это на стороне Lua, даже если вы никогда не очистил исходную таблицу, не существует утечки. Lua как-то знает, чтобы избавиться от стола, прежде чем переназначить его.

Однако, если у вас есть ссылки на C#, прежде чем вы установите его в пустую таблицу, хотя lua удаляет его содержимое, ссылки C# не удаляются; по-видимому, он действует так, как если бы экземпляр создал совершенно новую таблицу. Что вам нужно сделать, чтобы сбросить таблицу является:

for key,value in ipairs(testTable) do 
    testTable[key] = nil 
end 

Ваша проблема на самом деле не утечка памяти, то, что ваш статический экземпляр Lua загружается несколько версий таблицы из-за тем, как она сбрасывается.

Смежные вопросы