Cairo problem: memory leak?
BlitzMax Forums/Brucey's Modules/Cairo problem: memory leak?
| ||
Hello! I am using Cairo within a multi-threaded environment (although it's guaranteed that all drawing happens within the main thread only!) - and facing a severe memory leak (approx 20MB per second!) First I thought, I might be using Cairo the wrong way (namely by creating a local TCairoImageSurface (from a fixed image) and then creating a local TCairo context (which is "destroy"ed later after having finished drawing into the image) whenever I need to draw new content - up to 30 times per second). So I started "caching" the TCairoImageSurface (the underlying image was cached anyway) but immediately ran into the following error message Simulator.debug.mt(10231,0xa000d000) malloc: *** error for object 0x22e6cd0: incorrect checksum for freed object - object was probably modified after being freed, break at szone_error to debug Simulator.debug.mt(10231,0xa000d000) malloc: *** set a breakpoint in szone_error to debug Does Cairo.destroy() free the ImageSurface internally? When I create a new Surface every time, everthing works fine (well, except the horrible memory loss) I am using Cairo under Mac OS X 10.4 on a Mac mini Intel Thanks in advance for any response! |
| ||
Well, I just cached the result of TCairo.create(Curface) as well - and used a Cairo.createContext()/destroy() pair whenever I have to draw something. Now it no longer crashes - but the horrible memory leak still persists! |
| ||
Hello again! I just checked that it is NOT a problem of BlitzMAX's internal GC. The following little test superstrict function ThreadFunction:Object (Data:Object) debuglog("Subthread: started") local i:int for i = 1 to 1000000 ThreadSubroutine() next debuglog("Subthread: stopping") end function function ThreadSubroutine() local Text:String = "", i:int for i = 1 to 1000 Text = Text + " "; next end function debuglog("MainThread: spawning Subthread") local Subthread:TThread = createThread(ThreadFunction,null) debuglog("MainThread: waiting for Subthread to finish") waitThread(Subthread) debuglog("MainThread: finishing")runs fine without wasting more and more memory |
| ||
Hmmm, Cairo seems to be *principally* wasting memory: whenever I cache some more objects (e.g. ImageSurfaces, FontFaces etc.) I get a lower leaking rate... And there must be even more: sometimes I don't see any output from Cairo - but when I recompile the (unmodified!) code and run again, I might get output again! Prior to caching, I always got a result. With caching, I sometimes get nothing - debugger output shows, that all Cairo items have been properly created, though. I'll check my code again (to see if I can find a workaround) but I'm afraid that Cairo needs some more testing... |
| ||
Hi! Do you have a small example I can test here? |
| ||
Hello Brucey! The program itself is quite large already - but let me see if I can get an excerpt. Just give me some time |
| ||
Brucey, try this one already (although not multi-threaded) - it's just a little "stress test" using one of your examples: SuperStrict Import BaH.Cairo Graphics 640,480,0 SetBlend ALPHABLEND SetClsColor 255,255,255 repeat Local cairo:TCairo = TCairo.Create(TCairoImageSurface.CreateForPixmap(256,256)) Local normalizeMat:TCairoMatrix = TCairoMatrix.CreateScale(256.0,256.0) cairo.SetMatrix(normalizeMat) Local xc:Double = 0.5 Local yc:Double = 0.5 Local radius:Double = 0.4 Local angle1:Double = 45.0 Local angle2:Double = 180.0 cairo.SetLineWidth(0.05) cairo.Arc(xc, yc, radius, angle1, angle2) cairo.Stroke() ' draw helping lines cairo.SetSourceRGBA(1,0.2,0.2,0.6) cairo.Arc(xc, yc, 0.05, 0, 360) cairo.Fill() cairo.SetLineWidth(0.03) cairo.Arc(xc, yc, radius, angle1, angle1) cairo.LineTo(xc, yc) cairo.Arc(xc, yc, radius, angle2, angle2) cairo.LineTo(xc, yc) cairo.Stroke() cairo.Destroy() Local image:TImage = LoadImage(TCairoImageSurface(cairo.getTarget()).pixmap()) Cls DrawImage image,MouseX() - 128,MouseY() - 128 Flip Until KeyHit(key_escape) EndOn my machine, this little application ended up with hundreds of MBs (then the activity display stopped showing and my machine was almost locked) |
| ||
With an additionaldelay(33)in the loop (which gives you s.th. around 30fps) I end up with approx 17MB loss/sec, after the internal GC managed to collect dead memory 3 times (and then gave up!?) |
| ||
Hey, with an explicit GCCollect() in the loop (I'm counting til 30, then doing a GCCollect) I managed to keep the memory consumption low! Let me try that in my actual application! [result] Unfortunately, that didn't help - I have the impression, that the memory loss rate was slightly reduced, but I still loose approx. 14MB/sec (with all caching switched off again) [/result] |
| ||
Brucey, something strange is going on, when trying the following example SuperStrict Import BaH.Cairo type TDummy field Dummy:int[] = new int[1000] end type global ThreadSync:TMutex = createMutex() global ThreadLocker:TSemaphore = createSemaphore(0) function ThreadFunction:Object (Data:Object) debuglog("Subthread: started") repeat waitSemaphore(ThreadLocker) lockMutex(ThreadSync) ' emulates modification of graphics tree within subthread local i:int for i = 1 to 1000 local Dummy:TDummy = new TDummy next unlockMutex(ThreadSync) forever end function debuglog("MainThread: spawning Subthread") local Subthread:TThread = createThread(ThreadFunction,null) Graphics 640,480,0 SetBlend ALPHABLEND SetClsColor 255,255,255 local i:int = 0 repeat postSemaphore(ThreadLocker) i = i+1 if (i = 30) then gccollect() i = 0 end if lockMutex(ThreadSync) ' emulates displaying the graphics tree within main thread Local cairo:TCairo = TCairo.Create(TCairoImageSurface.CreateForPixmap(256,256)) Local normalizeMat:TCairoMatrix = TCairoMatrix.CreateScale(256.0,256.0) cairo.SetMatrix(normalizeMat) Local xc:Double = 0.5 Local yc:Double = 0.5 Local radius:Double = 0.4 Local angle1:Double = 45.0 Local angle2:Double = 180.0 cairo.SetLineWidth(0.05) cairo.Arc(xc, yc, radius, angle1, angle2) cairo.Stroke() ' draw helping lines cairo.SetSourceRGBA(1,0.2,0.2,0.6) cairo.Arc(xc, yc, 0.05, 0, 360) cairo.Fill() cairo.SetLineWidth(0.03) cairo.Arc(xc, yc, radius, angle1, angle1) cairo.LineTo(xc, yc) cairo.Arc(xc, yc, radius, angle2, angle2) cairo.LineTo(xc, yc) cairo.Stroke() cairo.Destroy() Local image:TImage = LoadImage(TCairoImageSurface(cairo.getTarget()).pixmap()) Cls DrawImage image,MouseX() - 128,MouseY() - 128 Flip unlockMutex(ThreadSync) delay(33) Until KeyHit(key_escape) Endthe app once immediately(!) crashed while creating instances of TDummy (with plenty of memory left) I get the impression as if the problem would be related to the BlitzMAX GC - and Cairo just the package where it becomes appearant! Unfortunately, I need Cairo...and Threads...and the GC... |
| ||
Brucey, I may have got it! Try Cairo.selectFontFace("arial", 0, 0)- I'm afraid, that's a major memory leak! just add it to the main thread of the abovementioned example before Cairo.destroy. You may even remove the dummy object creation loop of the second thread! For your convenience: here is the full code (even without the second thread!) SuperStrict Import BaH.Cairo Graphics 640,480,0 SetBlend ALPHABLEND SetClsColor 255,255,255 local i:int = 0 repeat Local cairo:TCairo = TCairo.Create(TCairoImageSurface.CreateForPixmap(256,256)) Local normalizeMat:TCairoMatrix = TCairoMatrix.CreateScale(256.0,256.0) cairo.SetMatrix(normalizeMat) Local xc:Double = 0.5 Local yc:Double = 0.5 Local radius:Double = 0.4 Local angle1:Double = 45.0 Local angle2:Double = 180.0 cairo.SetLineWidth(0.05) cairo.Arc(xc, yc, radius, angle1, angle2) cairo.Stroke() ' draw helping lines cairo.SetSourceRGBA(1,0.2,0.2,0.6) cairo.Arc(xc, yc, 0.05, 0, 360) cairo.Fill() cairo.SetLineWidth(0.03) cairo.Arc(xc, yc, radius, angle1, angle1) cairo.LineTo(xc, yc) cairo.Arc(xc, yc, radius, angle2, angle2) cairo.LineTo(xc, yc) cairo.Stroke() cairo.selectFontFace("arial", 0, 0) ' <<<<<<<<<<<<<<<<<< cairo.Destroy() Local image:TImage = LoadImage(TCairoImageSurface(cairo.getTarget()).pixmap()) Cls DrawImage image,MouseX() - 128,MouseY() - 128 Flip delay(33) Until KeyHit(key_escape) End |
| ||
Your first example app seems to run fine here. (the single thread app). It certainly isn't leaking - according to the leak tool. Sure, it goes a bit wild with memory, but as far as I can see, that's mostly TImage frame memory. Do you really need to create/destroy contexts all the time? |
| ||
I've just tested your last one. It isn't leaking here. Can you email me a compiled version of that last test please? (so that I can run it here). are you using the latest SVN cairo with the latest BlitzMax? |
| ||
Hmmm, caching the FontFace helped getting rid of the nasty memory leak - but it seems as if I then also have to cache the Cairo context and the associated Surface, right? If I try to recreate the Cairo context during the next display phase, the application crashes. I tried to recreate the Cairo context because I still face the problem that - from time to time - I do not get any display at all (with caching - I did not have this kind of problem without caching). In such a case, I have to restart my application and sometimes (not always) I actually get the desired output) Do you have any idea? |
| ||
Brucey, sure - with or without debugging enabled? I'm running BlitzMAX 1.38 with MaxGUI 1.37 under MacOS X 10.4 on a Mac mini Intel My Cairo version should be quite new (< 6 weeks or so) but let me check that first...yes, it's cairo_1_23_src.zip |
| ||
Brucey, Email has been sent! Example is also leaking under Leopard... but...HEY...when COMPILED under Leopard (same BlitzMX/MaxGUI/Cairo version!) everything runs fine! Strange: even the GCC/C++ versions reported by BlitzMAX are the same! Do you have any idea? Static libs? |
| ||
Hmm, something weird is going on (under Leopard): I just rebuilt BlitzMAX/MaxGUI/Cairo and then recompiled the last example - and now it is leaking as well, not continuously (memory consumption goes down from time to time) but still quite heavily. Now I'm completely puzzled... [edit]oh - it seems that the BlitzMAX/MaxGUI/Cairo modules depend on the Multi-Threading settings: under Tiger, Multi-Threading was set, under Leopard not. After switching to Multi-Threading under Leopard, I had to rebuild all modules and - afterwards - the example was leaking again I have to stop here for the moment, but will continue as soon as possible[/edit] |
| ||
Your binary does indeed leak on my machine. And looking at the call-stack for the "live" blocks in RAM, it's related to FT... 12 untitled2.debug.mt FT_New_Face 11 untitled2.debug.mt FT_Open_Face 10 untitled2.debug.mt open_face 9 untitled2.debug.mt tt_face_init 8 untitled2.debug.mt sfnt_load_face 7 untitled2.debug.mt tt_face_get_name 6 untitled2.debug.mt tt_name_entry_ascii_from_utf16 5 untitled2.debug.mt ft_mem_realloc 4 untitled2.debug.mt ft_mem_qrealloc 3 untitled2.debug.mt ft_mem_alloc 2 untitled2.debug.mt ft_mem_qalloc 1 untitled2.debug.mt ft_alloc 0 libSystem.B.dylib malloc The version I have here is Cairo 1.9.4. (which is the dev version). I've not checked it in yet though - as I was working on getting the GL support built in (no, it's not as fast as the image stuff). Perhaps 1.9.4 works better than 1.8.8 in this regard. |
| ||
None of my tests are leaking. ...except that I couldn't get your mt example working because it crashes out when it tries to free the gl texture. |
| ||
Brucey, thanks for your effort - the problem with freeing the GL textures can be solved (that's another MT-related problem) see at the end of http://www.blitzmax.com/Community/posts.php?topic=89912 (another one of my lengthy Blog-like threads) I will now try and rebuild all my BlitzMAX modules without MT and see if the last example still leaks If not, then we are facing even more trouble when using MT (deep sigh) [edit]Meanwhile, I did the rebuild and the example STILL leaks - thus, it does NOT depend on multithreading or not![/edit] |
| ||
Brucey, can you send me your *recommended* version of Cairo and the Cairo wrapper (if it differs from mine) Thanks in advance! |
| ||
Ok, with caching (of Surface, Context and FontFaces) my "real" application does not seem to suffer from memory losses any more (physical and virtual memory consumption keep stable) - even without "aggressive" GC (i.e. explicit GCcollect invocations). Now, however, I am facing the problem, that I only get some output in approx. 1 of 5 program invocations. Do you have any idea why? [edit]I found the reason: my Cairo surface is based on a "dynamic image" but I did not (yet) embrace my drawing code by lockImage and unlockImage. With image locking, I always get a display - but I also get a memory leak again...(damn)[/edit] |