Understanding Blitz Memory Thread
BlitzMax Forums/BlitzMax Programming/Understanding Blitz Memory Thread
| ||
I'm going to see if I can figure out a little about how Blitz handles memory in this thread. The following is the test first principle, I recommend using it (especially with 3rd party libs):GCCollect() Print "Initial Memory = " + GCMemAlloced() 'Test the Splash Screen Create. For i = 1 To 100 pIScreen = TSplashScreen.Create(pTGameController) pIScreen.Destroy() Next GCCollect() Print "After GCGollect = " + GCMemAlloced() Output: Initial Memory = 38008 After GCGollect = 38008 No leaks.:) |
| ||
Ok, but right off the bat this doesn't make sense to me? I actually use a singleton "design pattern" to load my images and maintain a pointer to the image. For example:'Singleton Get Instance Function. Function GetInstance:TGuiBank() If g_pTGuiBank= Null g_pTGuiBank = New TGuiBank End If Return g_pTGuiBank End Function 'Load the Splash Image. Method LoadSplashImage:TImage() If m_pTSplashImage = Null m_pTSplashImage = LoadImage("incbin::../Images/GUI/splash.png", FILTEREDIMAGE | MASKEDIMAGE) End If Return m_pTSplashImage End Method This mean, the splash screen image is still in memory. So why does it report that no additional memory is being used? Thanks |
| ||
I don't see what you mean sorry. This does not show if the object holding m_pTSplashImage is still alive or not. |
| ||
It is still alive because within the create the following gets called:pTSplashScreen.m_pTLogoImage = TGuiBank.GetInstance().LoadSplashImage() Since, pTGuiBank is global and pTSplashImage is a field of the type TGuiBank it would still be alive. So why is the memory allocation not showing? Is it loading into video memory? |
| ||
Not sure why you're making it so complicated.Graphics 640,480 GCCollect() Print "First "GCMemAlloced() Local image:TImage = LoadImage("max.png") GCCollect() Print "Second " GCMemAlloced() 'DrawImage image , 0 , 0 'Flip 'GCCollect() 'Print "Third "GCMemAlloced() First and second GCMemalloced() will say the same thing. You load the image but don't use it so, my guess, is the compiler optimises and doesn't bother loading it. Now uncomment the last 4 lines and notice that, now the image is used, second gcmemalloced has increased. That's my theory anyway. |
| ||
Im of the opinion that any code you attach to a global field declaration, isnt run until the first instance of that type is created. (if thats a help) But I dont understand your explination of what your doing at all. So probably It wasnt helpful |
| ||
@tonyg, I'm not making it complicated; I'm using the code from my game. It is designed to only load the image into memory once no matter how many times you call the load. @H&K, if that were true i don't think I see any allocation of any of the objects unless I was rendering. For that matter, what if you load and object and then use midhandleimage on it? I wish there was some documentation on how memory allocation is being handled so we didn't have to guess. I'll try a few simple examples when I get a few minutes. |
| ||
Check this out! Using a global variables: SuperStrict graphics 1024,768,0 'Declare Variables. Global pTImage:TImage = Null GCCollect() Print "Initial Memory = " + GCMemAlloced() pTImage = LoadImage("Images/GUI/splash.png") 'pTImage = LoadImage("Images/GUI/loading.png") MidHandleImage(pTImage) GCCollect() Print "Memmory After LoadImage = " + GCMemAlloced() 'DrawImage pTImage , 0 , 0 'Flip 'GCCollect() 'Print "Third "GCMemAlloced() Delay(1000) Output: Initial Memory = 37456 Memmory After LoadImage = 37624 Using a local variable: graphics 1024,768,0 'Declare Variables. Local pTImage:TImage = Null GCCollect() Print "Initial Memory = " + GCMemAlloced() pTImage = LoadImage("Images/GUI/splash.png") 'pTImage = LoadImage("Images/GUI/loading.png") MidHandleImage(pTImage) GCCollect() Print "Memmory After LoadImage = " + GCMemAlloced() 'DrawImage pTImage , 0 , 0 'Flip 'GCCollect() 'Print "Third "GCMemAlloced() Delay(1000) Output: Initial Memory = 37492 Memmory After LoadImage = 37492 Explain that? |
| ||
It just takes some time to update the amount of memory allocated. Try this one with waitkey. The longer you wait before pressing a key the more memory is allocated. (until it allocates about 12000 bytes) Remove waitkey and it says nothing is allocated. Graphics 640,480,0 GCCollect() Print "Initial Memory = " + GCMemAlloced() Local a[10000] WaitKey() GCCollect() Print "Memmory After = " + GCMemAlloced() |
| ||
Why would it take time? |
| ||
Explain that? Is it because, now global, the compiler doesn't know what other linked program might access that image so *has* to include the loadimage? If you run... Graphics 640 , 480 GCCollect() Print GCMemAlloced() Local image:TImage = LoadImage("thisimageexists.png") ' global image:TImage=loadimage("thisimagedoesnot.png") GCCollect() Print GCMemAlloced() you'll notice they use the same mem allocation. Notice in debug mode that the image is ALWAYS loaded and the memory always changed. It makes sense to me but it's all theory. |
| ||
Short note on local: Those work a little different than global. Global -> System RAM local -> CPU memory located if there is room for it So unless you flood the cache, it won't be in RAM. But I think the error / missassumption you are making is: Loading image -> whole image in RAM and not only the reference (ie 4bytes) Because the image will normally reside in VRAM You would need to create something RAM based like an array etc. |
| ||
I have examined the memory management code pretty extensively trying to determine if it would be possible to make the existing memory manager thread safe. From what I see in code I think I can explain how it works. Mark, maybe kind enough to correct my anaysis since for some of it you have to look at the assembly genrated by the BMAX compiler to understand, and I may be intepreting the assembly code incorrectly. First off the difference between global and local variables. Global Variables Are allocated at assembly time in the Data Segment of the process by the Assembled code. Local Variables Are allocated on the Stack when declared and are allocated at run-time. Not the CPU Cache. as Dremora states. The algorimth for BlitzMax is a Hybrid Mark/Sweep + reference Counted Garbage Collector. It is also considered a Consevative collector as it only will call GCCollect automatically when you allocate memory. ie. New Object. See http://www.iecc.com/gclist/GC-faq.html for details about GC's. So if you allocate a Global Var and assign it to a type the reference count of object stored in variable is increased. The memory is in the HEAP mangage by the GC. If you call GCCollect it scans the pointers allocated on the heap. If the reference count is > 0 it marks it, so it wont be released. When you set the variable to null it reduces the reference and if zero Free's the memory. Local variables are a bit different in that they are on the Stack, if you allocate a Local Variable it doesnt use the reference count, unless you assign it to another variable. These get cleaned up by the GC by scanning the Stack and the registers of the CPU. The garbage collector scans the Regsiters and the stack for references to live objects in the HEAP. If the address is in the stack or register it Marks the Object as still alive. This is why when you set a local variable to null and call Collect it might not get collected as there may still be reference in a register. Overall its a fairly effecient Garbage Collection schema. Based upon the way its structured currently with the reference counting done by compiler for globals there is no way to make it thread safe, with out changing the compiler. There is more too it in the way Freelists are handled but that is not so much a GC function as a Memory Managment function but the basics are outlined above. I think Mark goal has been balancing pretty well the convenice of a Garbage Collector vs speed of manual memory managment. Ie. New/Delete. Although I dont care for the limitation of the reference counting portion of it, vs a pure Mark/Sweep. I have been considering playing around with this replacing the Blitzmax Collector with this one. http://www.hpl.hp.com/personal/Hans_Boehm/gc/ It is a Mark/Sweep that implements Generational or Incremental Collection. This one would stubb out the refrence counting and would allow for cyclical references. Tests out pretty well in C/C++ but cant get a good performance test in BMAX without coding it in. It can also be made thread-safe. Although being thread-safe would reduce performance. This is heavy Computer Science on the links I provided but I think they can give you a good idea on how it works. Doug Stastny |
| ||
Hmm, move the mouse to increase memory allocated...Graphics 640,480,0 Local a[10000] Repeat GCCollect() DrawText GCMemAlloced() , 1 , 1 Flip Cls Until KeyHit(key_escape) I'm wondering whats a good way to get the real amount of memory allocated? (ie without just moving the mouse around until the number stops going up)? |
| ||
I've asked this before and I was hold no; but I figure it doesn't hurt as ask again. Is there anyway to looks at the reference count? I would like to see what objects Max is holding on to; rather than blindly trying to fix these allocation issues. Thanks |
| ||
I'm guessing the problem with autoGC is not knowing what it's doing and when. Personally, as long as memory allocation is not simply increasing without ever fluctuating then it seems OK. Are there any solidly recreatable leaks or is it a worry that there's some stuff hanging around which should have been reclaimed but isn't? . You could set GCSetMode to 4 although not sure how useful the output is. |
| ||
Sean: No there isn't any way to do that. This data is not exported as users would tend to write their own "delete fake method" or other strange stuff like this for sure. tonyg: I don't know of any memory leaks anymore, most have been fixed between 1.06 and 1.16 (string had some funny leaks). Most so called memory leaks that appear are by unexperienced users that create CStrings and forget to free them or that create memory blocks and forget to free them, which both is not GC managed and thus no leak |
| ||
Code to see the reference count.Strict Type TestObject Field Blah : Int=66 End Type Function Count(o:object) local p:int ptr = int Ptr(byte ptr o)-1 const BBZERO:Int=-2147483648 Print p[0]-BBZERO end function Function Showlocal() Print "Local Reference Counting" local l:TestObject= New TestObject Count(l) local l2:TestObject=l Count(l) end function Function ShowGlobal() Print "Global Reference Counting" Global g:TestObject= New TestObject Count(g) Global g2:TestObject =g Count(g) end Function ShowLocal() Showglobal() Here is code to show you but this example demonstrates what I was describing with Locals vs Globals it doesnt work the same way. Locals are on stack not reference counted Globals are. There is really no useful way to track memory usage with reference count checking. Unfortunatly with GC you really have to trust its working right. Biggest mistake with BMAX you can make is cyclical references or using handles. Doug Stastny |
| ||
Are there any solidly recreatable leaks or is it a worry that there's some stuff hanging around which should have been reclaimed but isn't? Stuff is just not being reclained, mostly because of cyclical references that are not being cleaned up properly. Man, I miss C++, when you told it to delete something it did! |
| ||
Have you got a link to any posts with more info about the non-reclaim problems? If not, can you post an example? |
| ||
Sean: Main problem about C++ is that if you don't tell it to delete it does not ... and this small little thing crashes win98 / winME that often that C++ programmers really should get send to moon sometimes :-) Main not reclaim problem happens when you set a TList to null without clearing it before ... for some reason the delete method on TLists is never called so all stuff saved within the list will never be freed again as the whole list is saved as cyclic linked list and can't be accessed anymore (so the whole thing keeps itself alive on its own). the only way to solve the cyclic reference problem would be the addition of a root link counter, that checks if the object can be accessed at all. (at least he only solution I know of and which is normally used) |
| ||
Dreamora, you keep posting that but I'm not sure why. Am I missing something... Type ttest Global testlist:TList = CreateList() Field x End Type Print GCCollect() Print "Before : " + GCMemAlloced() For x = 0 To 20000 mynew:TTest = New ttest ListAddLast ttest.testlist , mynew Next Print GCCollect() Print "During : " + GCMemAlloced() ttest.testlist = Null Print GCCollect() Print "After : " + GCMemAlloced() ? |
| ||
Its possible that it was fixed with one of the many syncmods last week. (once to twice a day some kind of fix came in) the main problem about this freeing problem is, that it isn't possible to "securely recreate" it, sadly, as its the GC that is either calling the delete or not. But the problem isn't coming up that often now, only now and then. In a case it isn't calling it, clear isn't called automatically on the list as well, which is why I'm going the secure way of calling it manually before null-ifying a list. it does not cost anything but gives me the guarantee that the tlinks are gone. |
| ||
hi Sean Doherty I understand why you want to use that Singleton Design Pattern. It's a good habit to use DP in programming anyway. If i am not mistaken, all the reply to you especially (tonyg) is that, although you load image to memory, and your debug log still showing same memory amount is because of the Code Optimization Feature of the BlitzMax Compiler itself. let's see how it work here. GCCollect() Print "Initial Memory = " + GCMemAlloced() pIScreen = TSplashScreen.Create(pTGameController) GCCollect() Print "Initial Memory = " + GCMemAlloced() if you are a compiler and you do Optimization work as compiler, you know that, TGameCOntroller has created and never used, so you can simply take it out and your Optimized code will become as follow GCCollect() Print "Initial Memory = " + GCMemAlloced() GCCollect() Print "Initial Memory = " + GCMemAlloced() the compiler has removed ( pIScreen = TSplashScreen.Create(pTGameController)) because it's simply doing nothing actually, and it will give same result with it or without it. it's called code optimization. (Code Optimization will most probably work on Machine Opcode level.) so you better create that object, make sure to show that object's image on the screen to make it sure code optimization will not neglect that object. and check the results. |
| ||
I'd hazard a guess that he's sorted it by now, since this thread is over a year old. Its not good practice to keep digging up ancient threads like you're doing. You've only made ten posts and this is the third time you've done it. |