I got tired of the endless (very small) arguments over whether it’s faster to use NSEnumerator or a for loop to enumerate an NSArray, so I thought I’d make a test and answer the question once and for all. http://goo.gl/OeSCu
/* enumerator_speed_test.m
Enumeration speed tester
This program tests the speed of NSEnumerator, compared with
using a for loop to enumerate over the NSArray.
Compile with this command:
cc -framework CoreFoundation -framework Foundation -std=c99 -o enumerator_speed_test enumerator_speed_test.m */
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#include
void PrintUsage(char *name)
{
fprintf(stderr, “usage: %s
int main(int argc, char **argv) { id outerPool = [NSAutoreleasePool new]; id tempPool; clock_t startTime, endTime;
if(argc != 3)
PrintUsage(argv[0]);
int arraySize = atoi(argv[1]);
int iterations = atoi(argv[2]);
// create a large array we can enumerate over
tempPool = [NSAutoreleasePool new];
NSMutableArray *mutArray = [NSMutableArray array];
for(int i = 0; i < arraySize; i++)
[mutArray addObject:@"test!"];
NSArray *array = [mutArray copy]; // create immutable copy
[tempPool release];
// test NSEnumerator speed
fprintf(stderr, "Enumerating through array with %d elements, %d iterations, using NSEnumerator...", arraySize, iterations);
startTime = clock();
for(int i = 0; i < iterations; i++)
{
tempPool = [NSAutoreleasePool new];
NSEnumerator *enumerator = [array objectEnumerator];
id obj;
while((obj = [enumerator nextObject]))
; // do nothing
[tempPool release];
}
endTime = clock();
fprintf(stderr, "done.\nTotal time taken: %f sec\n\n", (float)(endTime - startTime)/CLOCKS_PER_SEC);
// test for loop speed
fprintf(stderr, "Enumerating through array with %d elements, %d iterations, using for loop...", arraySize, iterations);
startTime = clock();
for(int i = 0; i < iterations; i++)
{
tempPool = [NSAutoreleasePool new];
unsigned arrayCount = [array count];
for(unsigned j = 0; j < arrayCount; j++)
{
id obj;
obj = [array objectAtIndex:j];
}
[tempPool release];
}
endTime = clock();
fprintf(stderr, "done.\nTotal time taken: %f sec\n\n", (float)(endTime - startTime)/CLOCKS_PER_SEC);
fprintf(stderr, “Enumerating through array with %d elements, %d iterations, using CFArray for loop…”, arraySize, iterations); startTime = clock(); for(int i = 0; i < iterations; ++i) { const unsigned arrayCount = CFArrayGetCount((CFArrayRef)array); for(unsigned j = 0; j < arrayCount; ++j) { id obj = (id) CFArrayGetValueAtIndex((CFArrayRef) array, j); } } endTime = clock(); fprintf(stderr, “done.\nTotal time taken: %f sec\n\n”, (float)(endTime - startTime)/CLOCKS_PER_SEC);
fprintf(stderr, "Enumerating through array with %d elements, %d iterations, using objectAtIndex: function pointer for loop...", arraySize, iterations);
startTime = clock();
for(int i = 0; i < iterations; ++i) {
SEL selector = @selector(objectAtIndex:);
id (*pFn)(id, SEL, unsigned);
pFn = (id (*)(id, SEL, unsigned)) [array methodForSelector:selector];
const unsigned arrayCount = [array count];
tempPool = [NSAutoreleasePool new];
for(unsigned j = 0; j < arrayCount; ++j)
{
id obj = (id) pFn(array, selector, j);
}
[tempPool release];
}
endTime = clock();
fprintf(stderr, "done.\nTotal time taken: %f sec\n\n", (float)(endTime - startTime)/CLOCKS_PER_SEC);
fprintf(stderr, "Enumeratinng through array with %d elements, %d iterations, using nextObject function pointer and NSEnumerator...", arraySize, iterations);
startTime = clock();
for(int i = 0; i < iterations; i++)
{
tempPool = [NSAutoreleasePool new];
NSEnumerator *enumerator = [array objectEnumerator];
SEL selector = @selector(nextObject);
id (*pFn)(id, SEL);
pFn = (id (*)(id, SEL))[enumerator methodForSelector:selector];
id obj;
while((obj = pFn(enumerator, selector)))
; // do nothing
[tempPool release];
}
endTime = clock();
fprintf(stderr, "done.\nTotal time taken: %f sec\n\n", (float)(endTime - startTime)/CLOCKS_PER_SEC);
[outerPool release];
return 0; }
Run the test like so:
Hope:~/shell mikeash$ ./enumerator_speed_test 10000000 10 Enumerating through array with 10000000 elements, 10 iterations, using NSEnumerator…done. Total time taken: 8.810000 sec
Enumerating through array with 10000000 elements, 10 iterations, using for loop…done. Total time taken: 10.280000 sec
The first parameter is the size of the array to create, and the second array is the number of enumerations to perform for each test.
Which is faster seems to depend on the size of the array. The ~15% variance displayed in the sample run is the largest I’ve seen in my quickie testing. They’re normally very close. NSEnumerator tends to be faster with larger arrays, and the for loop tends to be faster with smaller arrays, which is basically what I expected.
If anybody would like to take this program and do some kind of more detailed study, that would be cool, but hopefully this can put the “No, do it the other way, it’s faster!” comments to rest. - MikeAsh
;-)
./enumerator_speed_test 10000000 10 Enumerating through array with 10000000 elements, 10 iterations, using NSEnumerator…done. Total time taken: 14.240000 sec
Enumerating through array with 10000000 elements, 10 iterations, using for loop…done. Total time taken: 16.780001 sec
Enumerating through array with 10000000 elements, 10 iterations, using CFArray for loop…done. Total time taken: 11.160000 sec
Enumerating through array with 10000000 elements, 10 iterations, using objectAtIndex: function pointer for loop…done. Total time taken: 11.290000 sec
FYI, I was compiling with -O3, just to be fair. Damn, my machine is slow… ;-(
Anyone tried to single step through NSEnumerators nextObject to see what it does? as mentioned elsewhere, NSArray is based on CFArray, and CFArray has no (at least public) interface for enumeration, so I’d think that NSEnumerator just uses objectAtIndex:.
That said, you choose strategy based on the code, not the speed. Any overhead NSEnumerator has is constant, and it’s marginal compared to all the other constant overheads of using a high level language with dynamic dispatch.