Garbage Collection and nested lists

BlitzMax Forums/BlitzMax Programming/Garbage Collection and nested lists

Drackbolt(Posted 2010) [#1]
Got this buzzing around in my head, so I thought I'd start a discussion on it (don't see another thread on it). It's probably logical to a trained programmer but I was surprised by it.
Run this while watching the memory taken by the program in task manager at each step:
Graphics 20,20,0,60
Type yUnit
	Field ID%
	Field linkAll:TLink
	Field linkTarget:TLink
	Field ListTarget:TList = CreateList()
	Field Array%[10000]	'this is just to fill memory to prove concept
EndType
Global GListUnit:TList = CreateList()

Print "Ready to create units"
WaitKey()
Local N%, fU:yUnit
For N = 1 To 1000	'create 1000 units and add them to global list
	fU = New yUnit
	fU.ID = N
	fU.linkAll = ListAddLast(GListUnit,fU)
Next
Print "Ready to add targets"
WaitKey()
Local fU2:yUnit, R%
For fU = EachIn GListUnit	'now give each yUnit a random object target for the nested list
	For N = 1 To 5	'get five targets
		R = Rand(1,1000)	'pick a random target
		If R <> fU.ID
			For fU2 = EachIn GListUnit
				If fU2.ID = R	'found a match, add it to the target list
					fU2.linkTarget = ListAddLast(fU.ListTarget,fU2)
					Exit
				EndIf
			Next
		EndIf
	Next
Next

Print "Ready to clear"
WaitKey()
ClearList GListUnit
GCCollect()
Print "GListUnit cleared"
WaitKey()

If you run this, you should see that clearing the parent list and collecting garbage doesn't actually do any good, because the links on the zombie objects still reference each other in the target lists. I had figured clearing a "higher" list would clear the lower ones but that doesn't appear to be the case.
Can anyone explain why this behavior could be beneficial? It is expected? Right now I'm just finding it annoying...


Oddball(Posted 2010) [#2]
You are creating circular references. You aren't clearing the lists within your yUnit objects so all the objects are still referenced by those lists which in turn means the links still reference the objects, which in turn means the objects still reference the links, and so on, and so on.

Last edited 2010


Czar Flavius(Posted 2010) [#3]
In the simple case you have one 'main' reference to an object, in a global or data structure of some kind, although you may have several temporary fleeting references to them as needed (locals and function parameters). To the program however, there is no difference between these two types. So long as at least one reference exists, the object will live.

Circular references are a problem - you must break the chain by setting at least one reference to null. Circular references are often indictive of poor design, but sometimes neccesary. I have a remove method of my objects that clears any circular links and calls remove of any objects that depend upon this object for existence.

You can also turn multithreading on as that uses a different garbage collector that can handle circular references (i think)

Avoid task manager to record memory, sometimes windows overallocates memory to programs to quicly satisfy future requests. There is a function to tell you the amount of dynamic memory allocated, GCSomething.. You can also have a global count variable and increase it by one in New and decrease it by one in Delete, which is called when an object is collected. Don't put any program logic in Delete as you don't know when it will be called, make your own remove method you call yourself.


Drackbolt(Posted 2010) [#4]
Thanks for the replies.

As a matter of fact this code was already run threaded, so it doesn't seem that's any help in this particular case.

Thanks for the tip; I see GCMemAlloced() is the function. I'll try using that too to get more precise measurements.

It makes sense for circular references to be a problem now; I guess my brain was still hung up a bit on Blitz3d behavior. I only recently discovered I could store lists inside an object like this. However, I've tried clearing the links and lists and still see a ton of memory out there.

Please change the last part of the code to the following and tell me what is not being done correctly:
Print "Ready to clear"
Print GCMemAlloced()
WaitKey()
ClearList GListUnit
For fU = EachIn GListUnit
	ClearList(fU.listTarget)
	RemoveLink(fU.linkAll)
	fU.linkAll = Null
	RemoveLink(fU.linkTarget)
	fU.linkTarget = Null
Next
GCCollect()
Print "GListUnit cleared"
Print GCMemAlloced()
WaitKey()

I must be missing something stupid.

EDIT: Yes I missed something stupid, I have the list cleared before clearing the others, durr... Pay no attention to me. I think I'm getting the hang of things now.

Last edited 2010

Last edited 2010


Drackbolt(Posted 2010) [#5]
I do have one more question though. With the above changes (corrected by putting "Clearlist GListUnit" AFTER the freakin iteration that calls it), there is still a little bit of memory left over as compared to the amount it began with. Am I not clearing everything out or is this just to be expected? It's not much, it's just noticeable.


Czar Flavius(Posted 2010) [#6]
Use this to keep track of objects more carefully.

Type TUnit
	Global debug_count = 0	
	
	Method New()
		debug_count :+ 1
	End Method
	
	Method Delete()
		debug_count :- 1
	End Method
End Type


Last edited 2010


dmaz(Posted 2010) [#7]
don't worry about extra memory than you think still being allocated. that's the GCs job and it will do it when it thinks it should even if you call a collect. you only need to worry about actual leaks that continue to cause your max allocated to rise. http://www.blitzbasic.com/Community/posts.php?topic=88184#1001102


Drackbolt(Posted 2010) [#8]
Ok. Thanks for the responses guys!