Oberon space

General Category => Общий раздел => Тема начата: ilovb от Апрель 28, 2013, 04:10:41 pm

Название: Течет память
Отправлено: ilovb от Апрель 28, 2013, 04:10:41 pm
Есть у меня код под LuaJIT:
local ffi = require("ffi")
ffi.cdef [[
void free(void *ptr);
]]

require 'miniz_h'
local miniz = ffi.load("miniz")

local function inflate(source)
    local decomp_len = ffi.new 'size_t[1]'
    local pdata = ffi.gc(miniz.tinfl_decompress_mem_to_heap(source, #source, decomp_len, 0), ffi.C.free)
    return pdata ~= ffi.NULL, ffi.string(pdata, ffi.cast("int", decomp_len[0]))
end

local function NewZipWriter(fpath)
   
    local zip = ffi.new 'mz_zip_archive'
    zip.m_file_offset_alignment = 0
    if miniz.mz_zip_writer_init_file(zip, fpath, 0) == miniz.MZ_FALSE then
        return nil
    end

    function write(dpath, data, level)
        if miniz.mz_zip_writer_add_mem(zip, dpath, data, #data, level) == miniz.MZ_FALSE then
            mz_zip_writer_end(zip)
            os.remove(fpath)
            return false
        end
        return true
    end

    function finalize()
        if miniz.mz_zip_writer_finalize_archive(zip) == miniz.MZ_FALSE then
            miniz.mz_zip_writer_end(zip)
            os.remove(fpath)
            return false
        end 
        miniz.mz_zip_writer_end(zip) 
        return true
    end

    return {
        write = write,
        finalize = finalize
    }

end

Работает отлично. Память не течет.

Реализовал это в виде нативной луашной либы:
#include <ctype.h>
#include <lauxlib.h>
#include <lua.h>
#include <stdlib.h>
#include <string.h>
#include <miniz.h>

/*
 * ** compatibility with Lua 5.2
 * */
#if (LUA_VERSION_NUM == 502)
#undef luaL_register
#define luaL_register(L,n,f) \
               { if ((n) == NULL) luaL_setfuncs(L,f,0); else luaL_newlib(L,f); }

#endif

// static int lmz_inflate(lua_State *L);

static int lmz_inflate(lua_State *L) {
   
    size_t decomp_len;
    size_t comp_len;
    void *pDecomp_data;
    const char* pComp_data;
   
    comp_len = 0;
    pComp_data = luaL_checklstring(L, 1, &comp_len);

    pDecomp_data = tinfl_decompress_mem_to_heap(pComp_data, comp_len, &decomp_len, 0);
   
    lua_pop(L, 1);
   
    if (!pDecomp_data) {
        lua_pushnil(L);
        return 1;
    }
   
    lua_pushlstring(L, (char*)pDecomp_data, decomp_len);
   
    free(pDecomp_data);
   
    return 1;
}

static mz_zip_archive *checkzip(lua_State *L) {
      void *pZip = luaL_checkudata(L, 1, "lmz.zip_writer");
      luaL_argcheck(L, pZip != NULL, 1, "'zip_writer' expected");
      return (mz_zip_archive*)pZip;
}

static int lmz_new_zip_writer(lua_State *L) {

    const char *pZip_filename;
    mz_zip_archive *pZip;

    pZip_filename = luaL_checklstring(L, 1, NULL);

    lua_pop(L, 1);

    // размещаем в стеке указатель на структуру
    pZip = (mz_zip_archive*)lua_newuserdata(L, sizeof(*pZip));
    memset(pZip, 0, sizeof(*pZip));

    if (!mz_zip_writer_init_file(pZip, pZip_filename, 0)) {
        lua_pushnil(L);
        lua_pushstring(L, "Failed creating zip archive");
        return 2;
    }

    luaL_getmetatable(L, "lmz.zip_writer");
    lua_setmetatable(L, -2);

    return 1; 
}

static int lmz_zip_writer_write(lua_State *L) {

    const char *dpath;
    const char *data;
    size_t dsize;
    mz_zip_archive *pZip;
    mz_uint level;

    // при вызове метода zip:write()
    // первым в стек помещается 'zip'
    pZip = checkzip(L);

    dpath = luaL_checklstring(L, 2, NULL);
    data  = luaL_checklstring(L, 3, &dsize);
    level = luaL_checkint(L, 4);

    lua_pop(L, 4);

    if (!mz_zip_writer_add_mem(pZip, dpath, data, dsize, level)) {
        mz_zip_writer_end(pZip);
        lua_pushnil(L);
        lua_pushstring(L, "Failed add to zip archive");
        return 2;
    }

    lua_pushboolean(L, 1);

    return 1; 
}

// финализатор архива
static int lmz_zip_writer_finalize(lua_State *L) {

    mz_zip_archive *pZip;

    // при вызове метода zip:finalize()
    // первым в стек помещается 'zip'
    pZip = checkzip(L);

    if (!mz_zip_writer_finalize_archive(pZip)) {
        mz_zip_writer_end(pZip);
        lua_pushnil(L);
        lua_pushstring(L, "Failed creating zip archive");
        return 2;
    }

    mz_zip_writer_end(pZip);

    lua_pop(L, 1);

    lua_pushboolean(L, 1);

    return 1; 
}

// функции библиотеки
static const luaL_Reg miniz_functions[] = {
    { "inflate", lmz_inflate },
    { "new_zip_writer", lmz_new_zip_writer },
    { NULL, NULL }
};

// методы zip_writer
static const luaL_Reg miniz_zip_writer_methods[] = {
    { "write", lmz_zip_writer_write },
    { "finalize", lmz_zip_writer_finalize },
    { NULL, NULL }
};


LUALIB_API int luaopen_lmz(lua_State * const L) {

    // метатаблица для объекта zip_writer
    luaL_newmetatable(L, "lmz.zip_writer");
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);  /* pushes the metatable */
    lua_settable(L, -3);  /* metatable.__index = metatable */
   
    // регистрируем в метатаблице методы zip_writer
    luaL_register(L, NULL, miniz_zip_writer_methods);
   
    // регистрируем функции библиотеки
    luaL_register(L, "lmz", miniz_functions);

    return 1;
}

При использовании zip течет память. Хде повох? O_o
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 04:13:26 pm
Течет конкретно в lmz_zip_writer_write()
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 04:26:33 pm
Сначала думал на это место:
dpath = luaL_checklstring(L, 2, NULL);
    data  = luaL_checklstring(L, 3, &dsize);
    level = luaL_checkint(L, 4);

    lua_pop(L, 4);
Но в документации сказано:
Цитировать
const char *luaL_checklstring (lua_State *L, int narg, size_t *l);
Checks whether the function argument narg is a string and returns this string; if l is not NULL fills *l with the string's length.

This function uses lua_tolstring to get its result, so all conversions and caveats of that function apply here.
...
Because Lua has garbage collection, there is no guarantee that the pointer returned by lua_tolstring will be valid after the corresponding value is removed from the stack.
Т.е. достаточно lua_pop(L, 4) чтобы gc собрал data.
Ну и насильно free() тоже делал... не помогло
Название: Re: Течет память
Отправлено: Valery Solovey от Апрель 28, 2013, 04:28:22 pm
А checkzip обязательно так странно объявлять? По-другому работать не будет?

Что делает mz_zip_writer_end(pZip);

Он есть в if-е функции с течью, но его нет за if-ом (как в ...finalize).
Название: Re: Течет память
Отправлено: Valery Solovey от Апрель 28, 2013, 04:32:21 pm
Он есть в if-е функции с течью, но его нет за if-ом (как в ...finalize).
    if (!mz_zip_writer_add_mem(pZip, dpath, data, dsize, level)) {
        mz_zip_writer_end(pZip);
        ...
    }
    if (!mz_zip_writer_finalize_archive(pZip)) {
        mz_zip_writer_end(pZip);
        ...
    }

    mz_zip_writer_end(pZip);
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 04:35:18 pm
checkzip - это просто чтобы один и тот же код несколько раз не писать. (Это просто получение и проверка типа первого аргумента.)

mz_zip_writer_end - это завершение записи в архив (типа деструктор). Вызывать нужно только при ошибке и перед финализацией (записью таблицы содержимого zip)
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 04:36:17 pm
// Ends archive writing, freeing all allocations, and closing the output file if mz_zip_writer_init_file() was used.
// Note for the archive to be valid, it must have been finalized before ending.
mz_bool mz_zip_writer_end(mz_zip_archive *pZip);
https://code.google.com/p/miniz/source/browse/tags/v113/miniz.c#624
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 04:39:28 pm
... перед финализацией (записью таблицы содержимого zip)
вернее после финализации.
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 04:47:41 pm
Код на луа, юзающий эту либу:
local common = require 'common'
local lmz = require 'lmz'
local cf  = require 'cf_tools.cf_reader'

local SIG = string.char( 0xFF, 0xFF, 0xFF, 0x7F )

local level = {
    NO_COMPRESSION      =  0,
    BEST_SPEED          =  1,
    BEST_COMPRESSION    =  9,
    UBER_COMPRESSION    = 10,
    DEFAULT_LEVEL       =  6,
    DEFAULT_COMPRESSION = -1
}

local function UnpackTo(path, rd, zip)
   
    local Image = cf.ReadImage(rd)
    local res

    for ID, Body, Packed in Image.Rows() do
        if Packed then
            res = lmz.inflate(Body)
            if res then
                if res:sub(1, 4) == SIG then
                    UnpackTo(ID .. '/', cf.NewStringReader(res), zip)
                else
                    assert(zip:write(path .. ID, res, level.BEST_SPEED))
                end
            else
                print('inflate error', ID)
            end
        else
            assert(zip:write(path .. ID, Body, level.BEST_SPEED))
        end
    end

end

local file = arg[1] and assert(io.open(arg[1], 'rb'))

if file then
    local fdir, fnam, fext = common.parse_path(arg[1])
    local zip = assert(lmz.new_zip_writer(arg[2] or fdir..fnam..'zip'))
    UnpackTo('', cf.NewFileReader(file), zip)
    assert(zip:finalize())
else
    print 'Usage: cf_repack.lua myfile.cf [myfile.zip]'
end

На входе файлик 178 мег. На выходе zip 219 мег.
Старый код потребляет в среднем 6 мег. оперативы.
Новый код за несколько секунд взлетает до 1,5 гиг.
Название: Re: Течет память
Отправлено: Valery Solovey от Апрель 28, 2013, 04:54:57 pm
lua_pop(L, 4); А индексация с нуля или единицы? У L.
Если с нуля, то изначально в нём было 5 элементов, а извлечено 4.
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 05:00:40 pm
Тут не индекс, а количество аргументов. Т.е. выталкивается из стека 4 аргумента (pZip, dpath, data, level)
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 05:05:44 pm
Ну а про индексацию вот: http://www.lua.org/manual/5.1/manual.html#3.1
Название: Re: Течет память
Отправлено: Valery Solovey от Апрель 28, 2013, 05:15:44 pm
У меня идеи закончились.
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 05:57:31 pm
Писец... приехали...

Запустил этот код на интерпретаторе Lua - НЕ ТЕЧЕТ...
Походу бага в LuaJIT :(
Название: Re: Течет память
Отправлено: valexey_u от Апрель 28, 2013, 06:13:34 pm
Писец... приехали...

Запустил этот код на интерпретаторе Lua - НЕ ТЕЧЕТ...
Походу бага в LuaJIT :(
Не факт что бага. Возможно просто особенность реализации именно что интерпретатора, из за которой этой утечки там случайно нет.
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 06:27:38 pm
Гы гы  :D

Воткнул в цикле collectgarbage():
for ID, Body, Packed in Image.Rows() do
        if Packed then
            res = lmz.inflate(Body)
            if res then
                if res:sub(1, 4) == SIG then
                    UnpackTo(ID .. '/', cf.NewStringReader(res), zip)
                else
                    assert(zip:write(path .. ID, res, level.BEST_SPEED))
                end
            else
                print('inflate error', ID)
            end
        else
            assert(zip:write(path .. ID, Body, level.BEST_SPEED))
        end
    collectgarbage() -- СОБИРАЕМ МУСОР НАСИЛЬНО
    end
НЕ ТЕЧЕТ!

Походу LuaJIT так ентот цикл заворачивает, что gc совсем не вызывается
Название: Re: Течет память
Отправлено: ilovb от Апрель 28, 2013, 06:56:13 pm
Забавно. Вместо простого collectgarbage() делаю так:
if collectgarbage('count') > 50 * 1024 then
            print('collect!')
            collectgarbage()
        end
В результате в консоли ничего не печатает, но и память не течет. Комментирую строчку collectgarbage() и в консоль вываливает кучу 'collect!', память соответственно опять течет.