There are many common constructs that avoided to achieve better performance in YellowBox code. This section lists some of these and suggests ways to implement them more efficiently.
- (void) setFoo: (Foo *) aFoo; { [foo autorelease]; foo = [aFoo retain]; }This avoids the problem of potentially deallocating aFoo of foo and aFoo are the same instance. But, this can incure a large performance penalty if this method is called many times. In this case, it is better to write the method as:
- (void) setFoo: (Foo *) aFoo; { if (foo != aFoo) { [foo release]; foo = [aFoo retain]; } }
myDictionary = [NSDictionary dictionaryWithContentsOfFile: somePath]; [myCache setObject: myDictionary forKey: somePath];This is a convenient way to write this code, but in a performance critical situation, you can avoid autoreleasing the dictionary by writing
myDictionary = [[NSDictionary alloc] initWithContentsOfFile: somePath]; [myCache setObject: myDictionary forKey: somePath]; [myDictionary release];
NSEnumerator provides a convinient means for processing all of the elements in an NSArray (or other data structures). But when used in inner-loops, this can become very expensive. Calling -objectEnumerator returns a new autoreleased object. Additionally, NSEnumerator retains and then autoreleases each object that is returned from -nextObject. This is (presumably) meant to allow you to remove objects from data structures without having them deallocated (if their retain count was one at the time). This is a very dubious feature.
Rather than using NSEnumerator in this cases, an integer index will provide better performance with very little additional programming effort. Omni has abandoned use of NSEnumerators on NSArrays due to these issues.
In some situations it is not possible to easily avoid autoreleasing objects. This may be due to the fact that you are using a vendor supplied method that does not have a non-autorelased counterpart (-[NSDictionary objectEnumerator] for example) or perhaps doing so would make an API ugly.
In these cases it can be important to reduce the number of objects present in any one NSAutoreleasePool at a time. NSAutoreleasePool (in its current incarnation) maintains a set of vm_allocated pages that get filled with the addresses of the objects to autorelease. If you autorelease a large number of objects you are paying two prices, (i) the number of pages used by NSAutoreleasePool gets larger meaning that your application takes up more memory during the operation that is should and (ii) any objects that will be deallocated when the pool is released will have a longer life than necessary.
Consider the following example code with takes a list of paths to property list files, reads each into an NSDictionary and returns an NSDictionary of the requested property lists indexed by their path.
NSAutoreleasePool *pool; unsigned int pathIndex; NSMutableDictionary *dictionaryByPath; NSString *path; NSArray *paths; NSDictionary *dictionary; pool = [[NSAutoreleasePool alloc] init]; dictionaryByPath = [[NSMutableDictionary alloc] init]; pathIndex = [paths count]; while (pathIndex--) { path = [paths objectAtIndex: pathIndex]; dictionary = [[NSDictionary alloc] initWithContentsOfFile: path]; [dictionaryByPath setObject: dictionary forKey: path]; [dictionary release]; } [pool release];
This code follows the guidelines from above (don't use autoreleasing creation methods and don't use enumerators), but this can still have performance problems in some situations. If we end up reading many paths or reading a few really large paths, the internal implemenation of -[NSDictionary initWithContentsOfFile:] will become an issue. This method parses the given file and in doing so will autorelease some number of objects. If it turns out that we autorelease a large number of objects, then all of the burden of autoreleasing these objects will be taken by the single pool we allocated. Thus, we will fill a bunch of pages of memory with pointers to autoreleased objects and the objects themselves and then when the pool gets released it will have to touch all of those pages again in order to release the objects. This will increase the working set of the application and can cause the ending virtual size of the application to increase since any objects that were not transient during the operation will be scattered across the pages allocated during the operation. The transient objects will be deallocated leaving holes of unused memory between the objects we end up keeping.
This is easily solved by moving the pool inside the loop. Any transient objects allocated during one interation of the loop will be destroyed at the end of the loop. This will keep each pool smaller and will end up grouping the non-transient objects onto fewer pages of memory.