I'm losing my memory (GC & NSImageView)

  • I'm working on a simple image compositor application, and I have a
    memory usage/leak problem that I really can't pin down (I have garbage
    collection enabled).  The compositor has an NSImageView panel for
    the composited/filtered/scaled-down images, and an NSSlider for
    scrubbing back and forth through the frames.  The source images are
    numbered sequences of TGA files (typically 1280x720 pixels).
    Whenever the slider updates, it triggers a redraw.

    The problem is that within a very short period of moderate scrubbing
    (causing maybe 1000 redraws over a period of about a minute or two)
    I can fill up my G5's 5GB of RAM and cause the creation of multiple
    swap files.

    Here's the redraw code. At most it occurs once per event cycle:

      // Fetch the composition; bounds is an NSRect
      CIImage *imgResult = [layerManager renderFrame:frameNumber
    toSize:bounds];
      NSImage *image;
      NSCIImageRep *imgRep;

      // Crop the image to the required size
      CIFilter *crop  = [CIFilter filterWithName:@"CICrop"];
      CIVector *frameSize = [CIVector vectorWithX:0.0 Y:0.0
                            Z:bounds.size.width W:bounds.size.height];
      [crop setValue:imgResult forKey:@"inputImage"];
      [crop setValue:frameSize forKey:@"inputRectangle"];
      CIImage *finalImage = [crop valueForKey:@"outputImage"];

      // Create an NSImage from the result.
      imgRep = [NSCIImageRep imageRepWithCIImage:finalImage];
      image = [[NSImage alloc] initWithSize: NSMakeSize(bounds.size.width,
    bounds.size.height)];
      [image addRepresentation:imgRep];

      // Display the result; previewView is an NSImageView
      [previewView setImage:(NSImage*)image];

    Once this is done, the only thing holding on to the image is
    previewView,
    the NSImageView that displays the result.  When previewView gets a
    new image, I would expect the garbage collector to eventually dispose
    of the previous image, but there's little evidence this is occurring.
    On a
    couple of occasions I've seen some memory reclaimed, but not much.
    I know GC generally doesn't result in immediate clean up, but this is
    getting messy.

    ObjectAlloc (within Instruments) reckons I'm using far less memory (of
    the order of tens of megabytes).  Leaks shows a 'negligible' 500kB.
    I'm confused.

    I would be grateful to anyone who can shed some light on to what may
    be the cause of my problem, and eternally so to anyone who can help
    me overcome it.

    OT: OMG! ApKiDo won't launch on my machine!

    Stuart Rogers  /  Dual 2.5GHz G5 + 5GB RAM + OS X 10.5
  • You haven't given us enough information to know for
    sure what the problem is.

    You obviously suspect that the garbage collector is
    not running often enough, and you may be correct.  One
    of the most famous drawbacks to automatic garbage
    collection is the problem of knowing when it should
    run.  In this case, I suspect grabage collection only
    runs if control returns to the event loop.  Sliders
    and other controls do not return to the event loop
    until mouse up.

    Try calling your slider's action with
    performSelector:afterDelay: so that the event loop and
    the garbage collector run.

    In my case, one application produces real time
    animation, and a pause to garbage collect at the wrong
    time is disastrous.  On the flip side, if garbage is
    not collected often enough, memory use grows
    uncontrollably and eventually the system pauses
    because of swapping.

    The problem for me is always the "pause".  With manual
    memory management, the best/correct trade-off can be
    implemented in each case.

    It is good that Apple has provided an opt-in garbage
    collector.  Many types of application are well suited
    to automatic garbage collection and  are not affected
    by pauses.  Other applications can't tollerate it.
  • On Nov 4, 2007, at 7:51 AM, Erik Buck wrote:
    > You haven't given us enough information to know for
    > sure what the problem is.
    >
    > You obviously suspect that the garbage collector is
    > not running often enough, and you may be correct.  One
    > of the most famous drawbacks to automatic garbage
    > collection is the problem of knowing when it should
    > run.  In this case, I suspect grabage collection only
    > runs if control returns to the event loop.  Sliders
    > and other controls do not return to the event loop
    > until mouse up.

    That is incorrect.  The collector runs in a thread and, thus, will run
    regardless of the state of the main event loop.  In Foundation based
    tools, there is a call you can make to kick off threaded collections
    (see the documentation).

    From OP:
    > The problem is that within a very short period of moderate scrubbing
    > (causing maybe 1000 redraws over a period of about a minute or two)
    > I can fill up my G5's 5GB of RAM and cause the creation of multiple
    > swap files.

    You *might* be running into a situation where you have allocations
    outrunning the collector.  Maybe.  But probably not given the amount
    of time and relatively large size for few allocations that are typical
    to having lots of bitmaps floating around.

    This sounds more like a genuine leak, which is completely different
    under GC than non.

    > ObjectAlloc (within Instruments) reckons I'm using far less memory (of
    > the order of tens of megabytes).  Leaks shows a 'negligible' 500kB.
    > I'm confused.

    There are no leaks under GC.  GC breaks reference cycles and collects
    dead sub-graphs of objects.

    Or, more precisely, the notion of "leak" is defined differently under
    GC.  Under GC, a "leak" is when there is an unexpected strong
    reference to an object that is keeping it alive longer than
    expected.  The 'leaks' command line tool will not detect this because
    the objects are definitely *not* unreferenced.

    This situation typically arises when you have some kind of global
    cache or something -- direct or indirect -- that hangs onto the
    objects.  Often, a zeroing weak reference in the right spot will fix
    the issue.  Otherwise, you'll need to find an appropriate spot to
    prune the cache.  (There are new Foundation collection classes that
    explicitly support zeroing weak references.  They are quite useful for
    this.)

    GDB includes tools for examining the object graph and figuring out
    what is keeping your objects around for too darned long.  Once you
    find an object that is sticking around for too long, take the address
    and:

    info gc-references <ADDRESS/SYMBOL>

    Or:
    info gc-roots <ADDRESS/SYMBOL>

    In particular, gc-roots will tell you what globals (including stack
    references, if any) are rooting the object;  are directly or
    indirectly referencing the object and keeping it alive.

    And, of course, if you find that the root is something in an Apple
    framework, file a bug (http://bugreporter.apple.com/) and feel free to
    send me the bug # -- I may or may not respond directly, but you can be
    assured that I'll track any GC related issues as soon as they hit the
    bug tracker.

    On Nov 4, 2007, at 7:51 AM, Erik Buck wrote:
    > The problem for me is always the "pause".  With manual
    > memory management, the best/correct trade-off can be
    > implemented in each case.

    The Leopard Objective-C collector is fully asynchronous.  All scanning
    and collection, including finalization, is performed on a separate
    thread.  The only exception is a handful of classes that have
    finalizers that absolutely insist on running on the main thread (bad
    class! No pie for you!).

    > It is good that Apple has provided an opt-in garbage
    > collector.  Many types of application are well suited
    > to automatic garbage collection and  are not affected
    > by pauses.  Other applications can't tollerate it.

    Quite true.

    The overhead incurred by the collector should not cause any
    significant blocking pauses.  Of course, this is a 1.0 release and
    there are performance optimizations to be had.  Expect performance to
    improve over time.

    b.bum
  • From Eric:
    >> You haven't given us enough information to know for
    >> sure what the problem is.

    Isn't that par for the course for newbie posters?  I was hoping
    someone would chime in with some obscure 'gotcha' concerning
    GC and image views.

    >> You obviously suspect that the garbage collector is
    >> not running often enough, ... I suspect grabage collection only
    >> runs if control returns to the event loop.  Sliders
    >> and other controls do not return to the event loop
    >> until mouse up.

    The slider can (and was) configured to send continuous updates
    while the mouse is down.

    As Bill pointed out, the garbage collector runs asynchronously.
    And besides, even if it didn't, I would expect it to kick in once I
    stopped meddling with the slider.  I even tried prodding the
    collector by sending 'collectIfNeeded' during the code I posted.

    From Bill:
    >
    > You *might* be running into a situation where you have allocations
    > outrunning the collector.  Maybe.  But probably not given the amount
    > of time and relatively large size for few allocations that are typical
    > to having lots of bitmaps floating around.

    That was the first thing that came to mind, but it didn't free up any
    memory when I then let the app sit idle for a while.

    > This situation typically arises when you have some kind of global
    > cache or something -- direct or indirect -- that hangs onto the
    > objects.  Often, a zeroing weak reference in the right spot will fix
    > the issue.  Otherwise, you'll need to find an appropriate spot to
    > prune the cache.

    I'm not sure if there's anything I can do here - once the NSImage
    is created I pass it on to my NSImageView and forget it (I'm not
    caching it in my code).  When I feed it a new image, the old one
    should no longer have any strong references unless there's some
    caching going on by NSImageView (or something else within its
    framework).  This is where I start getting out of my depth.

    > GDB includes tools for examining the object graph and figuring out
    > what is keeping your objects around for too darned long.  Once you
    > find an object that is sticking around for too long, take the address
    > and:
    > info gc-references <ADDRESS/SYMBOL>
    > Or:
    > info gc-roots <ADDRESS/SYMBOL>
    >
    > In particular, gc-roots will tell you what globals (including stack
    > references, if any) are rooting the object;  are directly or
    > indirectly referencing the object and keeping it alive.

    I put a breakpoint in the above code and got what I would expect
    - one reference - but on (unrepeatable) occasions it would list
    dozens.  As I'm not familiar with GDB or what the data it provides
    means, I think I ought to go away and not come back until I am.

    This is only a hobby project, and it was curiosity that drove me to
    turn on garbage collection and see what happens, so if I can't work
    out the problem, it's no hardship to go back to the managed memory
    approach.

    Thanks for your comments - it's given me some things to think about.

    Stuart Rogers
  • Am 04.11.2007 um 23:56 schrieb Stuart Rogers:
    > I'm not sure if there's anything I can do here - once the NSImage
    > is created I pass it on to my NSImageView and forget it (I'm not
    > caching it in my code).  When I feed it a new image, the old one
    > should no longer have any strong references unless there's some
    > caching going on by NSImageView (or something else within its
    > framework).  This is where I start getting out of my depth.

      Is this NSImage loaded from a file ? In that case, NSImage does some
    cacheing under the hood, as far as I've been told. However, that's not
    a leak, you needn't worry about that. It does so both with and without
    GC, to speed up cases where the same image is requested over and over
    again.

    Cheers,
    -- M. Uli Kusterer
    http://www.zathras.de
  • On 05/11/2007, at 2:21 AM, Bill Bumgarner wrote:

    > GDB includes tools for examining the object graph and figuring out
    > what is keeping your objects around for too darned long.  Once you
    > find an object that is sticking around for too long, take the
    > address and:
    >
    > info gc-references <ADDRESS/SYMBOL>
    >
    > Or:
    > info gc-roots <ADDRESS/SYMBOL>
    >
    > In particular, gc-roots will tell you what globals (including stack
    > references, if any) are rooting the object;  are directly or
    > indirectly referencing the object and keeping it alive.

    This gets my vote for most useful tip of the month. I don't remember
    this being mentioned at the WWDC GC talk. I also can find no reference
    to it in the Documentation which I find astonishing as it is really
    incredibly useful for debugging garbage collection problems.

    I have already used this to find the source of a leak that has been
    causing me headaches for a month.

    Thanks, Bill.

    --
    Rob Keniger
  • On Nov 4, 2007, at 8:15 PM, Rob Keniger wrote:
    > This gets my vote for most useful tip of the month. I don't remember
    > this being mentioned at the WWDC GC talk. I also can find no
    > reference to it in the Documentation which I find astonishing as it
    > is really incredibly useful for debugging garbage collection problems.

    Excellent!  It is actually documented in the GDB release notes, but
    the documentation is rather terse.

    > I have already used this to find the source of a leak that has been
    > causing me headaches for a month.
    >
    > Thanks, Bill.

    Great!  Glad it helped!

    b.bum
  • >> I'm not sure if there's anything I can do here - once the NSImage
    >> is created I pass it on to my NSImageView and forget it (I'm not
    >> caching it in my code).
    >
    > Is this NSImage loaded from a file ? In that case, NSImage does
    > some cacheing under the hood, as far as I've been told. ... It does
    > so both with and without GC, to speed up cases where the same
    > image is requested over and over again.

    I so much wanted this to be the answer, so after creating the NSImage
    I inserted [image setCacheMode:NSImageCacheNever]; but it
    didn't make any difference.  But thanks for the pointer - I'm sure it'll
    come in useful to me in this project.

    For the record, the image is read from file via a CIImage - it then has
    various filters applied before being used to create the NSImage, which
    I understand is where the actual image file gets read from disc.

    Stuart
previous month november 2007 next month
MTWTFSS
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    
Go to today