views:

386

answers:

5

My data visualization app incurs a large memory consumption spike during redraw (setNeedsDisplay which triggers drawRect). I am currently redrawing the entire view that houses the data plot. This view is much larger then the device display.

Is there any way to tell CoreGraphics to allocate just enough memory to draw each element (each element is a small rectangular block much smaller then the device display) and release the memory when done, rather then my current naive approach?

Thanks in advance.

-Doug

UPDATE 8 Dec 8:28am EST

Here is the relevant code with explanatory wordage. I am running Instruments with ObjectAlloc, Memory Monitor, and Leaks instruments running. The only memory leak I have is due has to do with the NSOperationQueue not releasing mems. This is minor an not relevant.

Architecturally the app consists of a tableView with a list of interesting locations along the human genome to inspect. When a table row is selected I enqueue a data gathering operation that returns data called alignmentData. This data is then plotted as horizontal rectangular slabs.

Initially, when the tableView launches my memory footprint is 5 MB.

  • (void)viewWillAppear:(BOOL)animated {

    // Initial dimensions for the alignment view are set here. These // dimensions were roughed out in IB.

    frame = self.alignmentView.frame; frame.origin.x = 0.0; frame.origin.y = 0.0; frame.size.width = self.scrollView.contentSize.width; frame.size.height = 2.0 * (self.containerView.frame.size.height);

}

Note: After viewWillAppear: is called the memory footprint has not budged. Even though the alignmentView is be sized well beyond the dimensions of the display.

This is the method called from the data gathering operation.

  • (void)didFinishRetrievingAlignmentData:(NSDictionary *)results {

    // Data retrieved from the data server via the data gathering operation
    

    NSMutableData *alignmentData = [[results objectForKey:@"alignmentData"] retain];

    NSMutableArray *alignments = [[NSMutableArray alloc] init]; while (offset < [alignmentData length]) {

    // ...
    // Ingest alignmentData in alignments array 
    // ...
    

    } // while (offset < [alignmentData length]) [alignmentData release];

    // Take the array of alignment objects and position them in screen space // so that they pack densely creating horizontal rows of alignment objects // in the process. self.alignmentView.packedAlignmentRows = [Alignment packAlignments:alignments basepairStart:self.startBasepairValue basepairEnd:self.endBasepairValue]; [alignments release];

    [self.alignmentView setNeedsDisplay];

}

After this line of code:

self.alignmentView.packedAlignmentRows = ...

The memory footprint is 13.8 MB

After this line of code:

[self.alignmentView setNeedsDisplay];

The memory footprint spikes to 21.5 MB, stays there for a few seconds then returns to the pre-existing level of 13.8 MB

The solution I am looking for would allow me to essentially, create a horizontal render buffer window that that is the height of a single row of alignment objects. I would allocate its memory render into it, then discard it. I would do this over and over again for each row of alignment data.

In theory, I could render an infinite amount of data with this approach which of course would be most excellent ;-).

-Doug

+1  A: 

This is very possible, you will need to specify which sections of the screen need to be drawn, you need to call setNeedsDisplayInRect as described here and pass in a CGRect which is the area you wish to be redrawn.

This is much, much faster than re-drawing the entire screen, I had issues with this in an iPhone drawing application I created a year and a half ago.

Jay
Tried that. No joy Jay. Here is where I am at. Note, I'm doing all of this under the watchful eye of Instruments. I size my view in the viewWillAppear method by setting the frame. I then hit my data server, grab the data, cook it a bit. hand it to my view and call setNeedsDisplay. Boom! AN 8 - 10 megabyte hit. Then, interestingly, it returns to the memory footprint that preceeded the call to setNeedsDisplay. I tried this: [self.alignmentView setNeedsDisplayInRect:CGRectMake(0, 0, 24, 24)] which I thought would be insignificant. NO! The same 8 - 10 megabyte spike. I am completely stumped.
dugla
Sorry dugla, it's been over a year since my last commercial iPhone application and don't have the tools available to test here any more. I would suggest that it's possible that it's simply a back end iPhone function causing this memory spike. Perhaps try creating a test project and call a small screen refresh there. On another note, are you doing much in your drawRect method? It may not be the actual call but a resultant call that is causing this memory spike?
Jay
Jay, the mems spike happens even if I return immediately from drawRect. No drawing at all. Note: the memory spike did not happen when I sized the view (by setting its frame dimensions). So, even with nothing happening within the body of drawRect I get the mems spike. Sigh...
dugla
Sounds horrible mate, if you're returning straight after entering drawRect I don't really see how you're going to cut down on this. I would expect setNeedsDisplayInRect to reduce the amount of memory usage, but this memory must be being used in preparation for drawRect by default. Have you created a few test projects to see if this is common between different view sizes and drawing methods? That's all I can really suggest, I'm not sure of how your code could be causing such a spike unless it's behind the scenes in preparation for something which isn't actually required.
Jay
+1  A: 

"This view is much larger then the device display."

So you're scrolling through the data representation by moving the view around? You might want to consider making your view the same size as the display and using CGTranslate to adjust the drawing offset within your drawRect function. It sounds like you're drawing tons of stuff, and CoreGraphics can't tell what's visible and what is not.

You'll get much better drawing performance if you make the view smaller and insert checks to avoid drawing things that are outside the view's bounds.

Ben Gotow
+1 I was about to suggest the same.
Thomas Müller
Actually, drawing is insanely fast. I was thinking I would have to do bounds tests. Not so sure now. That is not my issue (at least not for now). Its all about the mems. Its killing me.
dugla
Could you post a representative sample of your code? It sounds like you're allocating tons of temporary objective c objects. If you post a bit of code, we might be able to point it out :-)
Ben Gotow
Will do. I will post an update to my question with code snippet shortly.
dugla
A: 

In addition to Ben's suggestion:

If you're scrolling around your data, consider adding a few smaller views to the scrollview. This way you don't need to redraw most of the time, but only when some area of your scrollview isn't covered any more. Basically, if one of your subviews scrolls completely out of sight you'd move it to the opposite side of the visible area and redraw it accordingly.

In my app I'm only scrolling horizontally, and am using two subviews. Let's say view1 is on the left and view2 on the right. When view2 scrolls out of sight, I move it to the left of view1 and redraw it accordingly. If the user scrolls further in the same direction view1 will scroll out of sight as well and I'll move it to the left of view2 and so on.

If you need to scroll horizontally and vertically you'd need 4 views.

Thomas Müller
I actually have a hybrid solution. I have a UIScrollView that has a containerView constrained to horizontal scrolling via overloading of setTransform. Currently it is a single large-ish view that will eventually be an "infinite scroller" that I've coded up but not yet incorporated into my viz app. The containerView has a child View that is vertically scrollable (not a UIScrollView, just my own simple scroller).
dugla
A: 

I know you are probably aware of this, but have you looked at the Core Plot framework for your data visualization? We recently added touch-scrolling of graphs and we've tried to be conservative when it comes to memory within the framework. Without knowing more about your specific case, this might be something you could try.

Brad Larson
+2  A: 

Here is the - not so obvious answer to my memory problem. I'll give myself this one because I learned it on the Apple dev forum form Rincewind - a very helpful Apple engineer BTW.

It turns out that by slicing a large view into N smaller pieces and rendering into each in turn I will incur a memory spike that is roughly 1/N the size of the large view.

So, for each smaller view: alloc/init, feed a portion of my data, setNeedsDisplay. Rinse/repeat for all N small views.

Simple, eh?

Prior to learning this I had mistakenly thought that setNeedsDisplay:myRect did this for the large view. Apparently not.

Thanks for all the suggestions gang.

Cheers, Doug @dugla

dugla