Concurrent NSArray enumeration using NSEnumerationConcurrent

If you have a data intensive app where you do a lot of JSON parsing, or, god forbid, XML or SOAP you may have noticed that you are not using all the processing power at hand.

Let’s look at a simple example of loading a long list of users:

NSURL* resourceURL = [[NSBundle mainBundle] URLForResource:@"users" withExtension:@"json"];
    
NSArray* usersRaw = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:resourceURL options:NSDataReadingMappedAlways|NSDataReadingUncached error:nil] options:0 error:nil];
    
NSMutableArray* users = [NSMutableArray new];
[usersRaw enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
    APUser* user = [APUser instanceFromDictionary:object];
        
    [users addObject:user];
}];

If you have a look at this code running in Instruments you will see the following:

Not concurrent

One of the cores on a dualcore CPU is always idle. You’re not doing anything for 50% of the time. Let’s fix that! Since NSMutableArray isn’t thread safe, we’ll also add a lock. You need to customize this to work with your own code. It’s not worth to trade a slow app for a faster one that’s crashing so take care and make sure your code is thread safe!

NSURL* resourceURL = [[NSBundle mainBundle] URLForResource:@"users" withExtension:@"json"];

NSArray* usersRaw = [NSJSONSerialization JSONObjectWithData:[NSData dataWithContentsOfURL:resourceURL options:NSDataReadingMappedAlways|NSDataReadingUncached error:nil] options:0 error:nil];

NSMutableArray* users = [NSMutableArray new];
NSLock *arrayLock = [[NSLock alloc] init];
[usersRaw enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id object, NSUInteger idx, BOOL *stop) {
    APUser* user = [APUser instanceFromDictionary:object];
    
    [arrayLock lock];
    [users addObject:user];
    [arrayLock unlock];
}];

Let’s have a look at the Instruments graph again. The CPU is now being utilized to it’s fullest potential!

Concurrent

The key is this part:

enumerateObjectsWithOptions:NSEnumerationConcurrent

What does this mean in actual numbers? The enumeration block runs in ~1 second instead of ~1.7 seconds on an iPhone 5s with a 37MB JSON file. While you may not be parsing that much data at once in your app, over the course of a long app run you can easily reach those figures, so I believe this will still make a difference. NSEnumerationConcurrent is available in iOS 4+ and Mac OS 10.6+ so it should be safe for pretty much anyone to use.

Leave a Reply

Your email address will not be published. Required fields are marked *