Performance Optimization: Why We Can't Use valueForKeyPath:

Recently I was coding away and found myself needing to access something three levels down in a dictionary. I immediately cringed at the thought of the following:

[[[info objectForKey:@"tile-data"] objectForKey:@"file-data"] objectForKey:@"_CFURLStringType"];

Why? Because it’s ugly. I mean, the code to get an object out of a dictionary completely buries which object I’m getting. It would be much nicer just to do this:

[info valueForKeyPath:@"tile-data.file-data._CFURLStringType"]

But I was wary enough to run some speed tests, using the harness written by Mike Ash for his performance tests. The results:

NameIterationsTotal time (sec)Time per (ns)
objectForKey x3100000005.0497.1
valueForKey x3100000005.6561.9

Looks like valueForKeyPath: is over ten times slower than the repeated objectForKey:. That kind of difference is pretty significant, even if you have to run the code 10 million times to see it. (For the curious, the objectForKey: and valueForKey: calls are on the same order as creating and releasing an NSAutoreleasePool; the valueForKeyPath: call a bit slower than creating an NSButtonCell. So it’s not that slow either way.)

This is pretty damning evidence. Looks like the prettier syntax is going to be sacrificed before I make it a habit.

Note: I did not make any special effort to remove all other factors in this test, such as length of the individual keys, number of keys in the path, or even what else was running on my computer at the time. (That last, I know, is quite serious, although I did avoid being active.) I simply tested the same dictionary (loaded once for each test, not included in the timing) with the key path given above. If the performance results were closer, I might have done a more precise test, but they weren’t, so I won’t.