1

Тема: Реальное удаление элементов таблицы - как?

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

Скрипт для теста:

function get_t_count(t)
  local c = 0
  for n in pairs(t) do 
    c = c + 1
  end
  return c
end

t = {}
for i=1,10000 do
  t[i] = i * 11
end

print (1, collectgarbage("count") , get_t_count(t), "<<-- создали, заполнили таблицу")

for i=1,8000 do
  t[i] = nil
end

print (2, collectgarbage("count"), get_t_count(t), "<<-- присвоили nil для 80% элементов таблицы")
collectgarbage ()
print (3, collectgarbage("count") , get_t_count(t), "<<-- почистили мусор")

for i=1,9000 do
  table.remove(t, 1)
end

print (4, collectgarbage("count") , get_t_count(t), "<<-- удалили 90% элементов таблицы")
collectgarbage ()
print (5, collectgarbage("count") , get_t_count(t), "<<-- почистили мусор")

t = {}
print (6, collectgarbage("count") , get_t_count(t), "<<-- переинициализировали таблицу {}")
collectgarbage ()
print (7, collectgarbage("count") , get_t_count(t), "<<-- почистили мусор")

Это скрипт выводит:

1    276.3134765625    10000   <<-- создали, заполнили таблицу
2    276.2890625       2000    <<-- присвоили nil для 80% элементов таблицы
3    276.1962890625    2000    <<-- почистили мусор
4    276.2880859375    2000    <<-- удалили 80% элементов таблицы
5    276.1962890625    2000    <<-- почистили мусор
6    276.3427734375    0       <<-- переинициализировали таблицу {}
7    20.1962890625     0       <<-- почистили мусор

Во втором столбце у нас - объем использованной Lua памяти.
Получается, что ни присвоение nil элементам таблицы, ни удаление элементов из таблицы через table.remove() не освобождает память! Даже если вызываем collectgarbage()
Помогает лишь полная переинициализация (удаление) таблицы через t = {}

И как же тогда быть? если в таблицу добавляем / удаляем элементы на протяжении работы скрипта - то занятая данными Lua память будет только расти и расти.

Есть ли способы реального освобождения памяти из-под элементов таблицы? гуглом ничего не сумел найти

2

Re: Реальное удаление элементов таблицы - как?

swerg пишет:

И как же тогда быть? если в таблицу добавляем / удаляем элементы на протяжении работы скрипта - то занятая данными Lua память будет только расти и расти.

Есть ли способы реального освобождения памяти из-под элементов таблицы? гуглом ничего не сумел найти

Привет! Таблицы созданные локально внутри блоков - тоже висят в памяти.
У вас все действия выполняются без задержек, а сборщик мусора работает по времени.

function main()
  while not exitflag do
    --some code--
   collectgarbage ()
   sleep(1)
  end
end

такое размещение collectgarbage вполне справляется с очисткой памяти

3 (2021-12-08 16:32:56 отредактировано swerg)

Re: Реальное удаление элементов таблицы - как?

kalikazandr, я не совсем понял ваш ответ, т.к. в нем присутствует  main() и sleep(), характерные для квика.
Я же привожу пример для "чистого" Lua. И вопрос просто про Lua как таковое.
Ну т.е. пусть у нас просто Lua-скрипт, который долгое время сам по себе работает; таблица в нем заполняется, часть элементов удаляется, добавляются новые (без выхода из скрипта). Как тогда?

Или вы говорите о том, что  collectgarbage() анализирует время, прошедшее с момента создания переменной и реально чистит из-под нее помять только про прошествии какого-то времени?
В самом деле: в Lua ведь нет отдельного потока, очищающего в фоне память, потому не очень понимаю что подразумевает фраза "сборщик мусора работает по времени". Могли бы ее растолковать чуть подробнее?

4

Re: Реальное удаление элементов таблицы - как?

swerg пишет:

фраза "сборщик мусора работает по времени". Могли бы ее растолковать чуть подробнее?

"Lua использует сборщик мусора, который запускается время от времени для сбора мертвых объектов, когда они больше не доступны из программы Lua."
Ключевое слово - время от времени и объекты, в вашем примере нет удаленных объектов.

5 (2021-12-08 21:40:36 отредактировано swerg)

Re: Реальное удаление элементов таблицы - как?

Спасибо за цитату.

"время от времени" - какая-то ну очень расплывчатая фраза.
А вот "для сбора мертвых объектов" вероятно как раз полностью отвечает на вопрос. Видимо таблица целиком есть единый объект Lua, и пока она не пуста - память из под таблицы не освобождается.

Это что касается моего примера.

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

6

Re: Реальное удаление элементов таблицы - как?

swerg пишет:

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

Фишка в том, что созданные локально внутри блока таблицы при выходе из блока удаляются не сразу, а "время от времени"
И без collectgarbage выделение памяти растет довольно таки быстро:

--memory_test.lua
local function test()
    local t = {}; return t
end
function main()
    while not exitflag do
        test()
        --collectgarbage()
        sleep(1)
    end
end
function OnStop()
    exitflag = true
end

7

Re: Реальное удаление элементов таблицы - как?

kalikazandr пишет:

Фишка в том, что созданные локально внутри блока таблицы при выходе из блока удаляются не сразу, а "время от времени"

В моём понимании, при вызове collectgarbage() наступает то самое "время от времени", ведь так? или я ошибаюсь?

kalikazandr пишет:

И без collectgarbage выделение памяти растет довольно таки быстро

Это понятно.
Но в моём исходном примере collectgarbage есть! Предлагаю рассуждать именно в рамках приведенного мою примера.
Из него видно, что даже после вызова collectgarbage память из-под таблицы, где были удалены элементы, не освобождается. Причем не освобождается вовсе. И видимо никогда (это про "время от времени"), пока таблица не будет полностью переинициализирована пустотой таблицей.
Так?

И если это так - то в типичном для QUIK сценарии "добавлять заявки в таблицу, после их исполнения удалять из таблицы", т.е. вести таблицу Lua со списком активных заявок, получается, что память под такую таблицу будет бесконечно расходоваться, без переиспользования.
И как тут быть?

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

8 (2021-12-10 15:09:08 отредактировано swerg)

Re: Реальное удаление элементов таблицы - как?

О! эксперименты показали, что память из-под удаленных элементов таблицы освобождается, когда в таблицу добавляется новый элемент. Не заменяется значение уже имеющегося, а именно добавляется новый элемент.
До добавления новых элементов память, занятая таблицей, не освобождается, даже удалить почти все элементы из неё.

Пример:

function get_t_count(t)
  local c = 0
  for n in pairs(t) do 
    c = c + 1
  end
  return c
end

t = {}
for i=1,10000 do
  t[i] = i * 11
end

print (1, collectgarbage("count") , get_t_count(t), "<<-- создали, заполнили таблицу")

for i=1,9000 do
  table.remove(t, 1)
end

collectgarbage ()
print (2, collectgarbage("count") , get_t_count(t), "<<-- удалили 90% элементов таблицы")

t[1] = 5.546

print (3, collectgarbage("count") , get_t_count(t), "<<-- заменили значение t[1]")

t[20000] = 1

print (4, collectgarbage("count") , get_t_count(t), "<<-- добавили элемент t[20000]")

local cnt = get_t_count(t)
for i=1,cnt*0.9 do
  table.remove(t, 1)
end

collectgarbage ()
print (5, collectgarbage("count") , get_t_count(t), "<<-- удалили 90% элементов таблицы")

t[30000] = 1

print (6, collectgarbage("count") , get_t_count(t), "<<-- добавили элемент t[30000]")

Вывод:

1    276.4326171875    10000    <<-- создали, заполнили таблицу
2    276.2353515625    1000     <<-- удалили 90% элементов таблицы
3    276.3271484375    1000     <<-- заменили значение t[1]
4    36.4140625        1001     <<-- добавили элемент t[20000]
5    36.2587890625     101      <<-- удалили 90% элементов таблицы
6    22.3720703125     102      <<-- добавили элемент t[30000]

Во втором столбце (дробное значение) - это занятая в Lua память.

На шаге 2, после удаления элементов таблицы и вызова collectgarbage() - занятая память не уменьшилась.
На шаге 3 память тоже не уменьшилась, т.к. мы заменили значение для уже имеющегося в таблице элемента [1].
А вот на шаге 4 занятая данными память в Lua уменьшилась, хотя мы добавили в таблицу один новый элемент t[20000]. Но в это момент Lua наконец-то переаллоцировала память под таблицу и освободила место.

9 (2021-12-10 18:38:59 отредактировано kalikazandr)

Re: Реальное удаление элементов таблицы - как?

swerg пишет:

Из него видно, что даже после вызова collectgarbage память из-под таблицы, где были удалены элементы, не освобождается. Причем не освобождается вовсе. И видимо никогда (это про "время от времени"), пока таблица не будет полностью переинициализирована пустотой таблицей.
Так?

высвобождается, ваш пример завершает работу раньше, чем сборщик мусора начал работать, заменил print на message и всё работает как надо wink
т.е. 1 раз вызывать collectgarbage перед sleep вполне достаточно.