#memory #memory-management #lua #garbage-collection #free
#память #управление памятью #lua #сбор мусора #Бесплатно
Вопрос:
Для справки, сценарии, описанные ниже, были выполнены с использованием следующего:
- Ubuntu 18.04.4 LTS (Bionic Beaver)
- top «procps-ng 3.3.12»
- Lua 5.3.3 Авторское право (C) 1994-2016 Lua.org , PUC-Рио
Каждый «сценарий» ниже демонстрирует результаты следующих шагов:
- Фиксируйте «lua5.3» начальное использование ресурсов процесса сверху
- В «lua5.3» выделите ~ 16 ГБ из кучи
- Зафиксируйте «lua5.3» использование ресурсов нового процесса из «top», чтобы показать, что было выделено 16 ГБ
- В «lua5.3» отметьте корневой объект для удаления, а затем вызовите «collectgarbage ()»
- Захват «lua5.3» конечного использования ресурсов процесса сверху, чтобы показать эффект «collectgarbage ()»
Для каждого сценария сравните начальное использование ресурсов процесса, «выделенное» использование ресурсов процесса и конечное использование ресурсов процесса после «collectgarbage(). Сценарий 1 показывает, что «выделенные» 16 ГБ НЕ освобождаются после сборки мусора. Сценарии 2 и 3 показывают, что часть «выделенных» 16 ГБ освобождается после сборки мусора. Сценарий 4 показывает, что все «выделенные» 16 ГБ освобождаются после сборки мусора. Сценарии с 1 по 4 отличаются только количеством объектов (т. Е. Таблиц), Используемых во время выделения, где сценарий 1 использует наибольшее количество таблиц (128 * 1024), а сценарий 4 использует наименьшее количество (1). Следовательно, похоже, что сборка мусора Lua 5.3.3 имеет ограничение на количество объектов, которыми он может действительно управлять, или есть дефект в управлении памятью Lua.
Короче говоря, сборка мусора Lua не показывает детерминированного поведения при управлении памятью. Если создается большое количество таблиц, использование памяти, связанное с этими таблицами, может никогда не вернуться в кучу, управляемую операционной системой, когда память больше не нужна в Lua. Однако, если создается небольшое количество таблиц, использование памяти, связанное с этими таблицами, похоже, ВСЕГДА возвращается в кучу, управляемую операционной системой, когда память больше не нужна в Lua.
Почему поведение управления памятью в Lua не является согласованным / детерминированным?
ПРИМЕЧАНИЕ: для каждого сценария вызов collectgarbage(«count») после collectgarbage() показывает, что lua5.3 правильно очистил весь мусор, тогда как использование «top» показывает, что это не всегда так. Следовательно, «top» используется здесь, чтобы показать истинное поведение сборки мусора Lua.
Сценарий 1: Lua collectgarbage() не освобождает используемую память
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
22866 user 20 0 18152 3140 2840 S 0.0 0.0 0:00.00 lua5.3
> collectgarbage("count")
22.8759765625
> a = {} for i=0,256*1024 do a[i] = {} for j=0,4*1024*1024 do a[i][j] = i*j end end
> collectgarbage("count")
16803927.791016
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
22866 user 20 0 16.053g 0.016t 2868 S 0.0 51.3 0:38.97 lua5.3
> a = nil collectgarbage()
> collectgarbage("count")
25.29296875
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
22866 user 20 0 16.049g 0.016t 2868 S 0.0 51.3 0:39.08 lua5.3
Scenario 2: Lua collectgarbage() frees half of memory used
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
22958 user 20 0 18152 3200 2916 S 0.0 0.0 0:00.00 lua5.3
> collectgarbage("count")
22.8759765625
> a = {} for i=0,128*1024 do a[i] = {} for j=0,8*1024*1024 do a[i][j] = i*j end end
> collectgarbage("count")
16790679.791016
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
22958 user 20 0 16.284g 0.016t 2944 S 0.0 52.0 0:39.79 lua5.3
> a = nil collectgarbage()
> collectgarbage("count")
23.1826171875
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
22958 user 20 0 8422324 8.018g 2944 S 0.0 25.6 0:40.50 lua5.3
Scenario 3: Lua collectgarbage() frees almost all memory used
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
23127 user 20 0 18152 3192 2904 S 0.0 0.0 0:00.00 lua5.3
> collectgarbage("count")
22.8759765625
> a = {} for i=0,64*1024 do a[i] = {} for j=0,16*1024*1024 do a[i][j] = i*j end end
> collectgarbage("count")
16784151.791016
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
23127 user 20 0 16.275g 0.016t 2932 S 0.0 52.0 0:41.22 lua5.3
> a = nil collectgarbage()
> collectgarbage("count")
23.1826171875
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
23127 user 20 0 25900 10992 2932 S 0.0 0.0 0:41.81 lua5.3
Scenario 4: Lua collectgarbage() frees all memory used
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
23771 user 20 0 18152 3204 2920 S 0.0 0.0 0:00.00 lua5.3
> collectgarbage("count")
22.8759765625
> a = {} for i=0,0 do a[i] = {} for j=0,1024*1024*1024 do a[i][j] = i*j end end
> collectgarbage("count")
16777241.969727
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
23771 user 20 0 16.017g 0.016t 2948 S 0.0 51.2 0:38.97 lua5.3
> a = nil collectgarbage()
> collectgarbage("count")
23.17578125
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME COMMAND
23771 user 20 0 18152 3364 2948 S 0.0 0.0 0:40.80 lua5.3
Update
After additional testing, it does appear that the number of objects (i.e. tables) managed in Lua affects the behavior of Lua’s garbage collection. Specifically, if the number of tables being managed by Lua is less than 256 (this seems to be the threshold), the memory will be returned to the operating system’s heap after garbage collection. As the number of objects grows above 256 (inclusive), Lua’s memory management begins to exhibit different behavior as it holds on to more memory for internal reuse after its garbage collection. Scenario 1 above shows the most dramatic affect of Lua holding onto memory after garbage collection, and Scenario 4 shows the least affect.
Note: The Example results are not deterministic; that is, other execution in Lua may affect the behavior of Lua’s garbage collection. For the most deterministic results, launch a new Lua process for each test.
For illustration, the following examples continuously allocate, populate, and deallocate a table of tables, printing out the following from the Lua process: iteration, preMemoryUsage (after allocation), preMemoryUsagePercent, postMemoryUsage (after deallocation), postMemoryUsagePercent.
Example Program: Change numberOfTables as Desired
numberOfTables = 255 -- Example 1: 255; Example 2: 256
function getMemoryPercent()
handle = io.popen("v=`ps -p $PPID -o pmem= | tr -d 'n' | sed 's/ *//g'`; echo -n $v")
memoryPercent = handle:read("*a")
handle:close()
return memoryPercent
end
socket = require("socket")
print("#", "preMemUsage", "%", "postMemUsage", "%")
iteration = 0
while true
do
a = {}
for i = 1,numberOfTables
do
a[i] = {}
for j = 1,math.floor(256*1024*1024/numberOfTables)
do
a[i][j] = i*j
end
end
preMemoryUsage = collectgarbage("count")
preMemoryUsagePercent = getMemoryPercent()
a = nil
collectgarbage()
postMemoryUsage = collectgarbage("count")
postMemoryUsagePercent = getMemoryPercent()
iteration = iteration 1
print(iteration, preMemoryUsage, preMemoryUsagePercent, postMemoryUsage, postMemoryUsagePercent)
end
Пример 1: Результаты для numberOfTables = 255
# preMemUsage % postMemUsage %
1 8355905.0869141 25.4 47.240234375 0.0
2 8355905.1972656 25.4 47.322265625 0.0
3 8355905.1972656 25.4 47.322265625 0.0
4 8355905.1972656 25.4 47.322265625 0.0
5 8355905.1972656 25.4 47.322265625 0.0
6 8355905.1972656 25.4 47.322265625 0.0
7 8355905.1972656 25.4 47.322265625 0.0
8 8355905.1972656 25.4 47.322265625 0.0
9 8355905.1972656 25.4 47.322265625 0.0
10 8355905.1972656 25.4 47.322265625 0.0
Пример 2: Результаты для numberOfTables = 256
# preMemUsage % postMemUsage %
1 4194369.0205078 12.8 47.119140625 0.0
2 4194369.1308594 12.8 47.201171875 12.8
3 4194369.1035156 12.8 47.173828125 12.5
4 4194369.1318359 12.8 47.2021484375 12.4
5 4194369.1318359 12.8 47.2021484375 12.4
6 4194369.1318359 12.8 47.2021484375 12.4
7 4194369.1318359 12.8 47.2021484375 12.4
8 4194369.1318359 12.8 47.2021484375 12.4
9 4194369.1318359 12.8 47.2021484375 12.4
10 4194369.1318359 12.8 47.2021484375 12.4
Комментарии:
1. Вероятно, собранная память доступна для других объектов Lua, но до сих пор не возвращена в ОС. Можете ли вы повторить тесты, используя двойной вызов
collectgarbage()
? Можете ли вы повторить тесты, используяcollectgarbage("count")
для получения использования памяти вместо выполненияtop
?2. @EgorSkriptunoff Я обновил запрошенную вами информацию. Повторные вызовы collectgarbage() не приводят к изменению поведения. Да, сначала я подумал, что это поведение типа Lua «виртуальная машина», которое позволяет Lua получать память из ОС, а затем просто управлять ею внутри. Но затем я понял, используя «top», что это поведение отличалось, когда я использовал небольшое количество таблиц. Кроме того, при использовании Lua в программе на C это проблематичная проблема управления памятью для процесса «C / Lua»; память, используемая в Lua, не всегда «освобождается» таким образом, чтобы часть «C» могла «повторно использовать» память.
3. Возможно, это не ошибка Lua, потому что Lua использует стандартный распределитель памяти из библиотеки C. Такое же поведение следует наблюдать, если вы работаете с кучей C вместо кучи Lua. И это означает, что память (которая не возвращается в ОС) может быть доступна для вашей программы на C.
4. Я выполнил аналогичный тест, используя только C, используя calloc() и free(); пока что это тестирование показало, что память возвращается в кучу, где «top» показывает, что память доступна. Для стороны Lua кажется, что я могу продолжать «повторно использовать» выделенную память в Lua. Но, как я уже сказал, бывают обстоятельства, при которых эта память может быть не в состоянии повторно использоваться C в смешанном процессе C / Lua.
5. Вам лучше опубликовать это в списке рассылки Lua.