Memory leak when loading graphic objects into table. Bug?

Hi Guys,
this code produces a significant memory leak for me. It loads 20 000 circles into a table and then removes them. The memory usage goes from 80kb to 3000 and then, instead of back to 80, to 600. Is this a bug or am I doing something wrong?

local points = {}  
  
timer.performWithDelay(1000, function()  
for i = 1, 20000 do  
 points[i] = display.newCircle(30,30, 4)  
end   
end)  
timer.performWithDelay(4000, function()  
for i = 1, #points do  
 display.remove(points[i])  
 points[i] = nil  
end   
end)  
  
 local monitorMem = function()  
 collectgarbage()  
 print( "MemUsage: " .. collectgarbage("count") )  
  
 local textMem = system.getInfo( "textureMemoryUsed" ) / 1000000  
 print( "TexMem: " .. textMem )  
end  
  
Runtime:addEventListener( "enterFrame", monitorMem )  

(Tried in the windows simulator in builds 726 and 730. The problem seems to exist when using images instead of circles as well.) [import]uid: 21937 topic_id: 20746 reply_id: 320746[/import]

I rewrote your snippet to make it loop over and over angain.
Seems that the mem load keeps stable after all.

local points = {}  
  
function createPoints ()  
 for i = 1, 5000 do  
 points[i] = display.newCircle(30,30, 4)  
 end   
 print ("mem before remove:")  
 monitorMem()  
end  
  
function removePoints ()  
 for i,val in pairs(points) do  
 val:removeSelf()  
 points [i] = nil  
 end  
 print ("mem after remove:")  
 monitorMem()  
 newLoop ()  
end  
  
function monitorMem()  
 collectgarbage()  
 print( "MemUsage: " .. collectgarbage("count") )  
 --local textMem = system.getInfo( "textureMemoryUsed" ) / 1000000  
 --print( "TexMem: " .. textMem )  
end  
  
function newLoop ()  
 timer.performWithDelay(1000, createPoints)  
 timer.performWithDelay(2000, removePoints)  
end  
 print ("mem on start:")  
 monitorMem()  
  
 newLoop ()  
  
--[[ output:  
mem on start:  
MemUsage: 79.2509765625  
  
mem before remove:  
MemUsage: 818.3896484375  
  
mem after remove:  
MemUsage: 818.2490234375  
  
mem before remove:  
MemUsage: 935.5771484375  
  
mem after remove:  
MemUsage: 818.2490234375  
  
...  
]]--  
  

I don’t know exactly how the LUA garbage collection works but I know it’s a bit slow sometimes. I also don’t know exactly what’s happening under the hood of corona, so the noticable but stable memory load that occurs after creating and removing display objects may be something we “just don’t get”…

Maybe anyone of the ansca team can enlighten us?

-finefin [import]uid: 70635 topic_id: 20746 reply_id: 81514[/import]

I find myself asking :

How much memory is due to starting with an empty array points{}, and then ending up with an array of 20000 pointers to nulls?

What happens if you nil the array or it goes out of scope?
[import]uid: 108660 topic_id: 20746 reply_id: 81528[/import]

The table cells themselves take up space. With 20,000 cells holding nil, that data structure if it’s just 32bit (4 bytes) pointers, is 160Kbytes alone. As it works out, using 600KB (- the 80KB initial size) you’re dealing with 26 bytes per table entry which is probably fair overhead for each cell.

This doesn’t seem like a leak to me.

[import]uid: 19626 topic_id: 20746 reply_id: 81529[/import]

thanks robmiracle, sounds plausible [import]uid: 70635 topic_id: 20746 reply_id: 81531[/import]

I ran an interesting test just now using this code:

print(gcinfo("count") \* 1024)  
  
t = {}  
  
print(gcinfo("count") \* 1024)  
  
for i=1,20000 do  
 t[i] = nil  
end  
  
print(gcinfo("count") \* 1024)  
collectgarbage("collect")  
  
print(gcinfo("count") \* 1024)  

This produces the following prints:

86016
86016
86016
78848

So you’re at 86K just to open a main.lua file. Interesting creating a table of 20000 nil’s didn’t add to the memory count and I’m not sure what garbage collection collected to get 8K back, but its apparently some memory freed up in initializing the code. However change it to:

print(gcinfo("count") \* 1024)  
  
t = {}  
  
print(gcinfo("count") \* 1024)  
for i=1,20000 do  
 t[i] = 10  
end  
  
print(gcinfo("count") \* 1024)  
  
for i=1,20000 do  
 t[i] = nil  
end  
  
print(gcinfo("count") \* 1024)  
collectgarbage("collect")  
  
print(gcinfo("count") \* 1024)  
  

and you get this:

86016
86016
479232
479232
472064

On this run I initialized the table to hold an integer number for each cell, then I called a 2nd loop to nil out the value since this represents you’re code better. Notice that the memory usage jumps significantly by adding a single number and nil’ing it out and running garbage collection doesn’t bring it back down again (well we recovered the same 8K we did on the first run). So this proves my theory that its just table overhead.

Just for fun, I ran a third test, this time doing a:

t = nil at the end and running garbage collection again.

The last two print statements, before the nil and after GC were:

473088
79872

I don’t know where I got an extra 1K of memory usage from but GC did clear up the memory from the table once I nilled the table.

[import]uid: 19626 topic_id: 20746 reply_id: 81544[/import]

Hi,

thanks a lot for the interesting and helpful answers!

robmiracle, While nilling out the table definetly seems to clear up the space if the table contained numbers, it doesn’t seem to work if I use circles instead. If I run the following code, the last print statement says 1171456.

[code]
print(gcinfo(“count”) * 1024)

t = {}

print(gcinfo(“count”) * 1024)
for i=1,20000 do
–t[i] = 10
t[i] = display.newCircle(0,0,2)
end

print(gcinfo(“count”) * 1024)

for i=1,20000 do
t[i]:removeSelf()
t[i] = nil
end

print(gcinfo(“count”) * 1024)
collectgarbage(“collect”)
t = nil
collectgarbage(“collect”)
print(gcinfo(“count”) * 1024)
[/code] [import]uid: 21937 topic_id: 20746 reply_id: 81555[/import]

Hi again.

In fact, the same thing seems to occur if I do not use a table at all:

[code]
print(gcinfo(“count”) * 1024)

for i = 1, 20000 do
local pt = display.newCircle(10,10,10)
pt:removeSelf()
pt = nil
end
collectgarbage(“collect”)
print(gcinfo(“count”) * 1024)

–prints: 94208
–then: 3090432

[/code] [import]uid: 21937 topic_id: 20746 reply_id: 81561[/import]

Warning: I’m coming down with a cold and my brain really isn’t in gear… :slight_smile:

The last one could very well could be a memory leak. I don’t know Lua/Corona internals, but you are reusing the same variable over and over at a very high speed before garbage collection has a chance to run.

The way I understand this is that calling removeSelf() followed by setting the variable to nil just marks the memory as available to be reclaimed the next time garbage collection runs. So if you reuse that variable before GC runs, the pointer to that memory will be lost.

[import]uid: 19626 topic_id: 20746 reply_id: 81591[/import]

Seems its an optimization technique inherent to Lua. The circles need to be stored in the currentStage table which is an array of all the display objects in the current scene. The currentStage table is not pre-allocated but is resized based on how many display objects you place at any particular time. Reallocating memory can be potentially a very costly event since you need to find a chunk of contiguous memory and this may require moving tables around. So when the table size exceeds the currently allocated size, Lua resizes it by a power of 2 (in our case seems to be in chunks of 1024 kb.) The important thing to realize here is that even if we set it to nil and remove it from the display scene the table still remains in memory. This is probably an optimization technique inherent to Lua. Here is some code that demonstrates this:

local circleCount = 1  
local previousMemory = gcinfo("count") \* 1024  
local function enterFrame()  
 for i = 1, circleCount do  
 local pt = display.newCircle(100,50,100)  
 pt:removeSelf()  
 pt = nil  
 end  
 circleCount = circleCount+1  
 collectgarbage("collect")  
 local diffMemory = gcinfo("count") \* 1024 - previousMemory  
 if(diffMemory \> 0) then  
 print("diff memory", diffMemory,"2^"..math.log(diffMemory)/math.log(2).."kb", " at "..circleCount.." circles ")  
 end  
 previousMemory = gcinfo("count") \* 1024  
end  
Runtime:addEventListener("enterFrame", enterFrame)  

We increase the number of cicles we add by 1 every frame and you can see that the table size increases (gets reallocated) every 6 circles. Suggesting each circle took apprx 170 kb.

-M.Y. Developers [import]uid: 55057 topic_id: 20746 reply_id: 81607[/import]

pure nerd-gold, everyone! thanks! [import]uid: 70635 topic_id: 20746 reply_id: 81699[/import]

robmiracle, you are totally right, if I add a collectgarbage after the nilling the memory comes down to 87kb again. Nice catch. :wink:

M.Y. developers, wow, thanks for that very technical answer! Very nice find, the corona behavior makes much more sense now. If you add a collectgarbage after the pt = nil here the memory remains constant too, which proves your point (if I understood it correctly) that the maximum number of concurrent circles count. Thanks again!

To fix my app, which generates circles on the trail of a physics object (similarly to angry birds) is to limit the maximum amount of circles. Like canupa found out, the extra memory then remains constant, and won’t pose any problem to the apps stability.

Again, thanks a lot everyone for helping me out! [import]uid: 21937 topic_id: 20746 reply_id: 81716[/import]