Перейти к основному содержимому

Поиск утечек памяти в Lua

· 3 мин. чтения

Перенос информации с /docs

Пришлось столкнуться. Мои микрозаметки на этот счет.

TL;DR

Все, что описано ниже может быть полезным, но в моем случае полезнее всего оказалось сбилдить LuaJit от Tarantool и использовать встроенный в него memory profiler (memprof), который покажет где и сколько памяти не высвободилось в коде.

Ручной поиск

В таймере каждую секунду принтить collectgarbage("count") / 1024, удаляя подозреваемые куски кода, пока память не перестанет "улетать в трубу". Такой себе способ.

-- Заметка для личного пользования

timer.Create("gc_count", 1, 0, function()
local freeMem = collectgarbage("count")
print("GC Count: " .. math.Round(freeMem / 1024, 2) .. " MB")
end)

Для таймера и Round используется библиотека lua-gmod-lib

Инструменты

Может быть интересно полистать: мини презентация по разным инструментам для поиска утечек

Я использую LuaMemorySnapshotDump + lua-microscope в связке.

LuaMemorySnapshotDump

LuaMemorySnapshotDump - делает файлы с дампом памяти. Чистый lua без сторонних модулей. Нужно сделать снимок "до" и "после" того, как часть памяти "уплыла", затем через этот же тул сделать diff

local mri = require("MemoryReferenceInfo")

mri.m_cConfig.m_bAllMemoryRefFileAddTime = false

print("dump BEFORE")

collectgarbage("collect")
mri.m_cMethods.DumpMemorySnapshot("./", "1-Before", -1)

print("waiting some time before next dump")

timer.Simple(120, function()
print("dump AFTER")
collectgarbage("collect")
mri.m_cMethods.DumpMemorySnapshot("./", "2-After", -1)

print("dump COMPARED")
mri.m_cMethods.DumpMemorySnapshotComparedFile("./", "Compared", -1, "./LuaMemRefInfo-All-[1-Before].txt", "./LuaMemRefInfo-All-[2-After].txt")

print("DONE BLYAT!!")
end)

В репе есть example файл и очень понятное readme. Конечный файл нужно выглядывать глазками. Выглядит примерно так:

--------------------------------------------------------
-- This is compared memory information.
--------------------------------------------------------
-- Collect base memory reference at line:-1@file:./LuaMemRefInfo-All-[1-Before].txt
-- Collect compared memory reference at line:-1@file:./LuaMemRefInfo-All-[2-After].txt
--------------------------------------------------------
-- [Table/Function/String Address/Name] [Reference Path] [Reference Count]
--------------------------------------------------------
string: "China" registry.2[_G].Author.Country[string] 1
function: 0x5618d4a99f10 registry.2[_G].Author.Ask[line:33@file:Example.lua] 1
string: "Beijing" registry.2[_G].Author.City[string] 1
string: "Game Developer" registry.2[_G].Author.Job[string] 1
string: "Game, Travel, Gym" registry.2[_G].Author.Hobby[string] 1
string: "yaukeywang" registry.2[_G].Author.Name[string] 1
table: 0x5618d4a7e5f0 registry.2[_G].Author 1

lua-microscope

lua-microscope – собирает информацию об указанной таблице, на выходе дает файл, который можно скормить GraphViz и получить КАРТИНКУ памяти

Быстрый тест:

git clone git@github.com:siffiejoe/lua-microscope.git

cd lua-microscope
$ cat > test.lua
local up1 = false
local up2 = io.stdout
local t1 = { val = 1 }
local t2 = { val = 2 }
setmetatable( t1, { __index = function( t, k )
if t2[ k ] ~= nil then
return t2[ k ]
else
return up1 or up2
end
end } )
setmetatable( t2, { __index = t1 } )

require( "microscope" )( "example1.dot", t1 )
lua test.lua

# http://www.graphviz.org/download/
dot -T jpeg -o example1.jpeg example1.dot

loom

У luajit есть модуль dump, запускается примерно так: luajit -jdump=T -e 'local s = 0; for i = 1, 100 do s = s + i end; print(s)'. Он генерирует .txt или .html файлик с информацией о "трассах памяти"(?). Короче, вот такие отчеты: клик.

На основе этих отчетов можно построить визуализации о работе jit компилятора.

Loom от CloudFlare улучшает эти отчеты, генерируя более подробный и красивый html файлик. Пальцем наугад я заметил в этих отчетах хоть только один, но очень важный участок кода, который поправил. Уверен, можно и больше, но пока что мне не хватает опыта.

Установка проста: git clone, mv jit $путь_в_пределах_package.path, mv loom.html $папка_с_initlua. Потом запускаем свой скрипт: luajit -jloom=loom.html,loom_dump.html init.lua