Garbage Collection and nested lists
BlitzMax Forums/BlitzMax Programming/Garbage Collection and nested lists
| ||
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... |
| ||
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 |
| ||
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. |
| ||
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 |
| ||
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. |
| ||
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 |
| ||
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 |
| ||
Ok. Thanks for the responses guys! |