Автор Тема: Течет память  (Прочитано 8640 раз)

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Течет память
« : Апрель 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

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Течет память
« Ответ #1 : Апрель 28, 2013, 04:13:26 pm »
Течет конкретно в lmz_zip_writer_write()

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Течет память
« Ответ #2 : Апрель 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() тоже делал... не помогло

Valery Solovey

  • Hero Member
  • *****
  • Сообщений: 509
    • Просмотр профиля
Re: Течет память
« Ответ #3 : Апрель 28, 2013, 04:28:22 pm »
А checkzip обязательно так странно объявлять? По-другому работать не будет?

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

Он есть в if-е функции с течью, но его нет за if-ом (как в ...finalize).

Valery Solovey

  • Hero Member
  • *****
  • Сообщений: 509
    • Просмотр профиля
Re: Течет память
« Ответ #4 : Апрель 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);

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Течет память
« Ответ #5 : Апрель 28, 2013, 04:35:18 pm »
checkzip - это просто чтобы один и тот же код несколько раз не писать. (Это просто получение и проверка типа первого аргумента.)

mz_zip_writer_end - это завершение записи в архив (типа деструктор). Вызывать нужно только при ошибке и перед финализацией (записью таблицы содержимого zip)

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Течет память
« Ответ #6 : Апрель 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

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Течет память
« Ответ #7 : Апрель 28, 2013, 04:39:28 pm »
... перед финализацией (записью таблицы содержимого zip)
вернее после финализации.

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Течет память
« Ответ #8 : Апрель 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 гиг.

Valery Solovey

  • Hero Member
  • *****
  • Сообщений: 509
    • Просмотр профиля
Re: Течет память
« Ответ #9 : Апрель 28, 2013, 04:54:57 pm »
lua_pop(L, 4); А индексация с нуля или единицы? У L.
Если с нуля, то изначально в нём было 5 элементов, а извлечено 4.
« Последнее редактирование: Апрель 28, 2013, 04:59:02 pm от Valery Solovey »

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Течет память
« Ответ #10 : Апрель 28, 2013, 05:00:40 pm »
Тут не индекс, а количество аргументов. Т.е. выталкивается из стека 4 аргумента (pZip, dpath, data, level)

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Течет память
« Ответ #11 : Апрель 28, 2013, 05:05:44 pm »
Ну а про индексацию вот: http://www.lua.org/manual/5.1/manual.html#3.1

Valery Solovey

  • Hero Member
  • *****
  • Сообщений: 509
    • Просмотр профиля
Re: Течет память
« Ответ #12 : Апрель 28, 2013, 05:15:44 pm »
У меня идеи закончились.

ilovb

  • Hero Member
  • *****
  • Сообщений: 2538
  • just another nazi test
    • Просмотр профиля
    • Oberon systems
Re: Течет память
« Ответ #13 : Апрель 28, 2013, 05:57:31 pm »
Писец... приехали...

Запустил этот код на интерпретаторе Lua - НЕ ТЕЧЕТ...
Походу бага в LuaJIT :(

valexey_u

  • Hero Member
  • *****
  • Сообщений: 3013
    • Просмотр профиля
Re: Течет память
« Ответ #14 : Апрель 28, 2013, 06:13:34 pm »
Писец... приехали...

Запустил этот код на интерпретаторе Lua - НЕ ТЕЧЕТ...
Походу бага в LuaJIT :(
Не факт что бага. Возможно просто особенность реализации именно что интерпретатора, из за которой этой утечки там случайно нет.
Y = λf.(λx.f (x x)) (λx.f (x x))