NSAutoreleasePool: it is teh awesomeness

One of the best kept secrets of ObjC/Cocoa it seems is the very very useful NSAutoreleasePool.

First off: what is an autorelease pool?

Well, whenever you tag an object for autorelease like so:

  [object autorelease]

it does not get released, instead it gets added to a big pool of objects and it is released at a later date. It is sort of like an implicit garbage collection. This is handy since it allows you to return values out of the scope of the creating methods etc..

You most often see autorelease in situations where you have alloced an object in a method and you want to return that object.

-(NSImage*)subImage:(NSImage*)source sourceRect:(NSRect)sourceRect 
{
	// set up our destination image
	NSSize destSize = NSMakeSize(NSWidth(sourceRect), NSHeight(sourceRect));
	NSRect destRect = sourceRect;
	destRect.origin = NSZeroPoint;
	
	// alloc our new image with the right size
	NSImage * dest = [[NSImage alloc] initWithSize:destSize];	

	// draw our sub image into our new image 
	[dest lockFocus];
	[source drawInRect:destRect fromRect:sourceRect operation:NSCompositeCopy fraction:1.0];
	[dest unlockFocus];
	
	// return and autorelease
	return [dest autorelease];
}

This is a method that I have in a quick utility I am working on that slices up mega images into smaller manageable tiles (in this case I am working with images in the 20k x 20k pixels range.) The last line is the autorelease, it should look familiar if you have done any amount of cocoa at all.

However, you may not realize just how many objects you are using that are autoreleased. Have a look at this NSImage saving to PNG method:

-(void)saveImage:(NSImage*)anImage toFile:(NSString*)filePath
{
	NSData *imageData = [anImage TIFFRepresentation];
	NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:imageData];
	NSDictionary *imageProps = [NSDictionary dictionaryWithObject:[NSNumber numberWithFloat:1.0] forKey:NSImageCompressionFactor];
	imageData = [imageRep representationUsingType:NSPNGFileType properties:imageProps];
	
	if (![imageData writeToFile:filePath atomically:YES]) NSLog(@"ACK!! Cannot write image");	
}

Almost every line in that method generates an autoreleased object. And in the case of image data, those objects can be very big.

OK great, so what actually happens to an autoreleased object? Every cocoa app has a big autorelease pool that holds all of the objects that are autoreleased within the scope of the pool (which in the case of the big one is your whole app) and if it gets full enough, or if there is a lull in processing it will go through and clean up the pool and get rid of all the autoreleased objects.

OK, sounds nifty, so how does this help us?

Well, you can alloc and release your own pools, creating your own mini-pools to help keep your memory to a minimum.

Here is a method that takes a very big bitmap image rep and slices it up into tiles and saves those tiles down to disk.

-(void)sliceImage:(NSBitmapImageRep*)veryBigRep resize:(NSSize)resize level:(NSInteger)level
{
	NSImage * resizeImage = [[NSImage alloc] initWithSize:resize];
	[resizeImage addRepresentation:veryBigRep];
		
	NSInteger xLocation = 0;
	NSInteger yLocation = resize.height - tileSize.height;
	
	NSInteger xIndex = 0;
	NSInteger yIndex = 0;
	BOOL processing = YES;
	
	NSRect tile = NSZeroRect;
	tile.size = tileSize;
	
	while (processing) {
		tile.origin.x = xLocation;
		tile.origin.y = yLocation;
		
		NSAutoreleasePool * apool2 = [[NSAutoreleasePool alloc] init];
		NSImage * subimage = [self subImage:resizeImage sourceRect:tile];
		
		[sliceImage performSelectorOnMainThread:@selector(setImage:) withObject:subimage waitUntilDone:YES];
		
		[self saveImage:subimage toFile:[self.saveFile stringByAppendingFormat:@"_%d_%d_%d.png",level,xIndex,yIndex]];
		
		[apool2 release];		
		
		yIndex ++;		
		yLocation -= tileSize.height;
		if (yLocation + tileSize.height < 0) {
			xIndex++;
			xLocation += tileSize.width;
			yIndex = 0;
			yLocation = resize.height - tileSize.height;			
		}
		if (xLocation >= resize.width) {
			processing = NO;
		}
	}
	[resizeImage release];
}

This method is a bit ugly, and really could be refactored a bit, but for a lame excuse, I just wrote it in a few minutes to do a few one-time image slicing, so I wont refactor it until i need to use it again….

anyway. Have a look in the middle of the loop, I am allocating an autorelease pool, then performing some fairly data intensive things, then releasing my pool. What this is doing is forcing the cleanup of all the autoreleased objects at the point which i release the pool.

Now, just to be clear, there are no leaks in this code. (at least not yet) all the objects are being alloced and released or autoreleased properly to keep memory from leaking. However, even with proper memory management you can still have memory problems if you dont use pools.

The entire reason I am writing this is because of this one problem I was having. I tend to use autorelease pools everywhere to help speed up performance, however, it was in this case that I required a pool. Without that pool in the loop, my app was crashing out. It was running out of memory.

Picture 3

Here are a few instrument runs showing my memory problems. When I was running my test images that were only 6k x 6k pixels, no problems. Once I started putting in really really big images, I was running out of memory space. and I dont mean actual RAM, but addressable space in 32 bits. (another solution is to build the app in 64 bit, but this is actually much easier)

the topmost instrument run was with the release pool in place (the bottom two were without), that is the only code difference between the runs. You can see on the top trace how much more even the object allocation and deallocation is. I think I just topped out at something like 800M of object space on the top trace.

On the other runs it was crashing out with fantastic errors like this one:

Attempt to allocate 33554432 bytes for NS/CFData failed
*** Terminating app due to uncaught exception ‘NSMallocException’, reason:
‘Attempt to allocate 33554432 bytes for NS/CFData failed’

So! use the pools! This is especially important for iPhone development. If you are doing anythign at all with images on an iPhone, then you should be closely guiding your memory usage with autorelease pools.

Here is a method out of my OpenGLES game harness for the iPhone:

-(void)loadAtlasData:(NSString*)atlasName
{
	NSAutoreleasePool * apool = [[NSAutoreleasePool alloc] init];	
	if (quadLibrary == nil) quadLibrary = [[NSMutableDictionary alloc] init];
	if (collisionMeshLibrary == nil) collisionMeshLibrary = [[NSMutableDictionary alloc] init];
	
	CGSize atlasSize = [self loadTextureImage:[atlasName stringByAppendingPathExtension:@"png"] materialKey:atlasName];

	NSArray * itemData = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:atlasName ofType:@"xml"]];
	
	for (NSDictionary * record in itemData) {
		BBTexturedMesh * quad = [self texturedQuadFromAtlasRecord:record atlasSize:atlasSize materialKey:atlasName];
		quad.atlasKey = [record objectForKey:@"name"];
		[quadLibrary setObject:quad forKey:[record objectForKey:@"name"]];
		if (([record objectForKey:@"collisionMesh"]) && ([[record objectForKey:@"collisionMesh"] count] > 2)) {
			BBCollisionMesh * cMesh = [self collisionMeshFromAtlasRecord:record];
			[collisionMeshLibrary setObject:cMesh forKey:[record objectForKey:@"name"]];
		}
	}
	[self bindMaterial:atlasName];
	[apool release];
}

Here I am building a whole bunch of objects by traversing a plist and then allocating objects and loading them into a big dictionary storage object. In this case I have wrapped an autorelease pool around the entire method. This allows me to control exactly when and how all those fairly big temporary arrays and constant strings and other autoreleased objects get released. This is important on the iPhone because you dont want to your autorelease pool to be holding onto memory that you dont need anymore, and you want to control when the release happens (since it does take some time to do it) so that you can perform those time-intensive tasks when the performance is not in a critical spot (like while you are trying to maximize your FPS. this is a bad time to have extra unused memory waiting to be released)

Ok, that is it for now, back to work :-)

Cheers!
-B

This entry was posted in Blog, code. Bookmark the permalink.

Leave a Reply