Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preferred way to make a mutable copy of a non-mutable object?

Tags:

ios

foundation

There are 2 choices (possibly more). Using NSSet as an example:

NSMutableSet * mutableSet = [ NSMutableSet setWithSet:nonMutableSet ] ;

or

NSMutableSet * mutableSet = [ [ nonMutableSet mutableCopy ] autorelease ] ;

Is there any difference between these two implementations? Is one "more efficient" at all? (Other examples would be NSArray/NSMutableArray and NSDictionary/NSMutableDictionary

like image 277
nielsbot Avatar asked Jan 24 '12 10:01

nielsbot


1 Answers

Benchmark fun! :)

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{ @autoreleasepool {
    NSMutableSet *masterSet = [NSMutableSet set];

    for (NSInteger i = 0; i < 100000; i++) {
        [masterSet addObject:[NSNumber numberWithInteger:i]];
    }

    clock_t start = clock();

    for (NSInteger i = 0; i < 100; i++) {
        @autoreleasepool {
            [NSMutableSet setWithSet:masterSet];
        }
    }

    NSLog(@"a: --- %lu", clock() - start);

    sleep(1);

    start = clock();

    for (NSInteger i = 0; i < 100; i++) {
        @autoreleasepool {
            [[masterSet mutableCopy] autorelease]; 
        }
    }

    NSLog(@"b: --- %lu", clock() - start);

    return 0;
} }

On my machine (10.7), setWithSet: is ~3x slower than -mutableCopy (Does somebody want to try on iOS 5? :) )

Now, the question is: why?

-mutableCopy is spending most of its time in CFBasicHashCreateCopy() (see CFBasicHash.m). This appears to be copying the hash buckets directly, with no rehashing.

Running  Time   Self  Symbol Name
256.0ms  61.5%  0.0   -[NSObject mutableCopy]
256.0ms  61.5%  0.0     -[__NSCFSet mutableCopyWithZone:]
256.0ms  61.5%  0.0       CFSetCreateMutableCopy
255.0ms  61.2%  156.0       CFBasicHashCreateCopy
97.0ms   23.3%  44.0          __CFSetStandardRetainValue

-setWithSet is enumerating through each value of the set, and then adding it to the new set. From the implementation of CFBasicHashAddValue (again in CFBasicHash.m), it looks like it is rehashing each value in the set.

Running    Time Self    Symbol Name
1605.0ms   86.0%    0.0     +[NSSet setWithSet:]
1605.0ms   86.0%    2.0       -[NSSet initWithSet:copyItems:]
1232.0ms   66.0%    68.0        -[__NSPlaceholderSet initWithObjects:count:]
1080.0ms   57.8%    299.0         CFBasicHashAddValue
324.0ms    17.3%    28.0        -[NSSet getObjects:count:]
272.0ms    14.5%    75.0          __CFBasicHashFastEnumeration

This rehash makes sense at the CFSet level. CFSets take a CFSetHashCallBack in the callBacks parameter; thus, two CFSets of CFNumbers could have a different hashing routine specified. Foundation's NSSet uses CFSet under-the-hood, and has a CFSetHashCallBack function which invokes -[NSObject hash]. (Although I guess that Apple could optimize this case and avoid the rehash when two sets have the same hash callback).

Note that this benchmark is for NSSet (of NSNumbers) only, other collection classes may have different performance characteristics.

like image 172
iccir Avatar answered Nov 15 '22 07:11

iccir