Suppose I have a NSDictionary with two sub collections of a NSArray and a NSDictionary:
NSMutableDictionary *mkDict(void){
NSMutableDictionary *dict=[NSMutableDictionary dictionary];
NSMutableDictionary *sub=[NSMutableDictionary dictionary];
NSMutableArray *array= [NSMutableArray array];
[dict setObject:array forKey:@"array_key"];
[dict setObject:sub forKey:@"dict_key"];
return dict;
}
There are a multitude of ways to access a single element of the sub-collection, and I choose to time three of them.
The first way is to indirectly access the subelements by accessing the key of the parent:
void KVC1(NSMutableDictionary *dict, int count){
for(int i=0; i<count; i++){
char buf1[40], buf2[sizeof buf1];
snprintf(buf1,sizeof(buf1),"element %i", i);
snprintf(buf2, sizeof buf2, "key %i", i);
[[dict objectForKey:@"array_key"]
addObject:
[NSString stringWithUTF8String:buf1]];
[[dict objectForKey:@"dict_key"]
setObject:[NSString stringWithUTF8String:buf1]
forKey:[NSString stringWithUTF8String:buf2]];
}
}
The second is to use KeyPath access:
void KVC2(NSMutableDictionary *dict, int count){
for(int i=0; i<count; i++){
char buf1[40], buf2[sizeof buf1], buf3[sizeof buf1];
snprintf(buf1,sizeof(buf1),"element %i", i);
snprintf(buf2, sizeof buf2, "key %i", i);
snprintf(buf3, sizeof buf3, "dict_key.key %i",i);
[dict insertValue:
[NSString stringWithUTF8String:buf1]
atIndex:i inPropertyWithKey:@"array_key"];
[dict setValue:
[NSString stringWithUTF8String:buf1]
forKeyPath:
[NSString stringWithUTF8String:buf3]];
}
}
And the third, similar to the first, is to access a pointer to the sub element, then use that pointer:
void KVC3(NSMutableDictionary *dict, int count){
NSMutableArray *subArray = [dict objectForKey:@"array_key"];
NSMutableDictionary *subDict = [dict objectForKey:@"dict_key"];
for(int i=0; i<count; i++){
char buf1[40], buf2[sizeof buf1];
snprintf(buf1,sizeof(buf1),"element %i", i);
snprintf(buf2, sizeof buf2, "key %i", i);
[subArray addObject:[NSString stringWithUTF8String:buf1]];
[subDict
setObject:
[NSString stringWithUTF8String:buf1]
forKey:
[NSString stringWithUTF8String:buf2]];
}
}
Here is the timing code:
#import <Foundation/Foundation.h>
#import <mach/mach_time.h>
// KVC1, KVC2 and KVC3 from above...
#define TIME_THIS(func,times) \
({\
mach_timebase_info_data_t info; \
mach_timebase_info(&info); \
uint64_t start = mach_absolute_time(); \
for(int i=0; i<(int)times; i++) \
func ; \
uint64_t duration = mach_absolute_time() - start; \
duration *= info.numer; \
duration /= info.denom; \
duration /= 1000000; \
NSLog(@"%i executions of line %i took %lld milliseconds", times, __LINE__, duration); \
});
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSMutableDictionary *dict=mkDict();
NSMutableDictionary *dict2=mkDict();
NSMutableDictionary *dict3=mkDict();
TIME_THIS(KVC1(dict,1000),10);
TIME_THIS(KVC2(dict2,1000),10);
TIME_THIS(KVC3(dict3,1000),10);
if([dict isEqualToDictionary:dict2])
NSLog(@"And they are the same...");
[pool drain];
return 0;
}
Here are the results:
10 executions of line 256 took 57 milliseconds
10 executions of line 257 took 7930 milliseconds
10 executions of line 258 took 46 milliseconds
And they are the same...
Question: Why is the OS X Snow Leopard / Lion suggested method of using KeyPaths so stinking slow? If you increase the size of count to 10,000 or more, KVC2 becomes infinitely slow where the other two methods increase linearly.
Am I doing something wrong? Is there a better idiom to access a single element of a sub collection in a dictionary?
In
KVC2(), you sendThe documentation for that method states the following:
Since the message is being sent to
dict, an instance ofNSDictionary, there is no-insertIn<Key>:atIndex:method, hence-mutableArrayValueForKey:is sent. The documentation for this method states the following:So what’s happening is that at each iteration:
If you use Instruments to profile your program, you’ll notice that about 50% of the processing time is spent on
-[NSKeyValueSlowMutableArray insertObject:atIndex:]— I think it’s safe to assume thatNSKeyValueSlowMutableArrayis the proxy array and its name should be a clue of its performance.