Search code examples
iosobjective-cnsarrayfilteringperformance-testing

Which has faster performance indexesOfObjectsPassingTest or filteredArrayUsingPredicate?


When needing to filter an NSArray to get a subset of the items in the array returned, which method is quicker more frequently and in edge cases?


Solution

  • The following tests (compiled in Release mode, executed on a Mac Pro) indicate that filteredArrayUsingPredicate is slower than indexesOfObjectsPassingTest if you use a "textual" predicate, but faster if you use block-based predicate. The fasted method in my test was a simple (fast-enumeration) loop that adds all matching objects to a mutable array.

    Results for filtering an array of 10,000,000 dictionaries, where about 50% match the predicate:

    8.514334 (predicateWithFormat)
    4.422550 (predicateWithBlock)
    5.170086 (indexesOfObjectsPassingTest)
    3.154015 (fast-enumeration + mutable array)
    

    Of course the results may be different for other predicates.

    #import <Foundation/Foundation.h>
    
    NSUInteger filter1(NSArray *a)
    {
        NSPredicate *pred = [NSPredicate predicateWithFormat:@"num > 1000 AND foo == 'bar'"];
        NSArray *filtered = [a filteredArrayUsingPredicate:pred];
        return [filtered count];
    }
    
    NSUInteger filter2(NSArray *a)
    {
        NSPredicate *pred = [NSPredicate predicateWithBlock:^BOOL(NSDictionary *obj, NSDictionary *bindings) {
            return ([obj[@"num"] intValue] > 1000 && [obj[@"foo"] isEqualToString:@"bar"]);
        }];
        NSArray *filtered = [a filteredArrayUsingPredicate:pred];
        return [filtered count];
    }
    
    NSUInteger filter3(NSArray *a)
    {
        NSIndexSet *matching = [a indexesOfObjectsPassingTest:^BOOL(NSDictionary *obj, NSUInteger idx, BOOL *stop) {
            return ([obj[@"num"] intValue] > 1000 && [obj[@"foo"] isEqualToString:@"bar"]);
        }];
        NSArray *filtered = [a objectsAtIndexes:matching];
        return [filtered count];
    }
    
    NSUInteger filter4(NSArray *a)
    {
        NSMutableArray *filtered = [NSMutableArray array];
        for (NSDictionary *obj in a) {
            if ([obj[@"num"] intValue] > 1000 && [obj[@"foo"] isEqualToString:@"bar"]) {
                [filtered addObject:obj];
            }
        }
        return [filtered count];
    }
    
    void testmethod(NSArray *a, NSUInteger(*method)(NSArray *a))
    {
        @autoreleasepool {
            NSDate *t1 = [NSDate date];
            NSUInteger count = method(a);
            NSDate *t2 = [NSDate date];
            NSLog(@"%f", [t2 timeIntervalSinceDate:t1]);
        }
    }
    
    int main(int argc, const char * argv[])
    {
        @autoreleasepool {
            NSMutableArray *a = [NSMutableArray array];
            for (int i = 0; i < 10000000; i++) {
                [a addObject:@{@"num": @(arc4random_uniform(2000)), @"foo":@"bar"}];
            }
            testmethod(a, filter1);
            testmethod(a, filter2);
            testmethod(a, filter3);
            testmethod(a, filter4);
        }
        return 0;
    }