Which has faster performance indexesOfObjectsPassingTest or filteredArrayUsingPredicate?
The following tests (compiled in Release mode, executed on a Mac Pro) indicate thatfilteredArrayUsingPredicate
is slower than indexesOfObjectsPassingTest
if you usea "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 matchingobjects 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;}
I tested this problem with the brand new Xcode 6 performance tests (Objective-C) with the test cases below. I got the following results indicating that the enumerationBlock with the flag NSEnumerationConcurrent
is the fastest filtering method for large arrays:
testPerformancePredicateWithFormat - measured [Time, seconds] average: 0.189testPerformancePredicateWithBlock - measured [Time, seconds] average: 0.093testPerformanceEnumerationBlock - measured [Time, seconds] average: 0.092testPerformanceIndexesOfObjectsPassingTest - measured [Time, seconds] average: 0.082testPerformanceFastEnumeration - measured [Time, seconds] average: 0.068testPerformanceEnumerationConcurrent - measured [Time, seconds] average: 0.036
Here the tests:
#import <XCTest/XCTest.h>@interface TestPMTests : XCTestCase@property(nonatomic, copy)NSArray *largeListOfDictionaries;@end@implementation TestPMTests- (void)setUp { [super setUp]; self.largeListOfDictionaries = [NSMutableArray array]; // Initialize a large array with ~ 300.000 entries as Dictionaries of at least one key value pair {"id":"<any id>"}}- (void)testPerformancePredicateWithFormat { NSString *ID = @"204440e5-4069-48e8-a405-88882a5ba27e"; NSPredicate *pred = [NSPredicate predicateWithFormat:@"SELF.id == %@", ID]; [self measureBlock:^{ NSArray *filtered = [self.largeListOfDictionaries filteredArrayUsingPredicate:pred]; NSLog(@"Count: %d", filtered.count); }];}- (void)testPerformancePredicateWithBlock { NSString *ID = @"204440e5-4069-48e8-a405-88882a5ba27e"; NSString *kID = @"id"; NSPredicate *pred = [NSPredicate predicateWithBlock:^BOOL(NSDictionary *d, NSDictionary *bindings) { return [d[kID] isEqualToString:ID]; }]; [self measureBlock:^{ NSArray *filtered = [self.largeListOfDictionaries filteredArrayUsingPredicate:pred]; NSLog(@"Count: %d", filtered.count); }];}- (void)testPerformanceIndexesOfObjectsPassingTest { NSString *ID = @"204440e5-4069-48e8-a405-88882a5ba27e"; NSString *kID = @"id"; [self measureBlock:^{ NSIndexSet *matchingIndexes = [self.largeListOfDictionaries indexesOfObjectsPassingTest:^BOOL(NSDictionary *d, NSUInteger idx, BOOL *stop) { return [d[kID] isEqualToString:ID]; }]; NSArray *filtered = [self.largeListOfDictionaries objectsAtIndexes:matchingIndexes]; NSLog(@"Count: %d", filtered.count); }];}- (void)testPerformanceFastEnumeration { NSString *ID = @"204440e5-4069-48e8-a405-88882a5ba27e"; NSString *kID = @"id"; [self measureBlock:^{ NSMutableArray *filtered = [NSMutableArray array]; for (NSDictionary *d in self.largeListOfDictionaries) { if ([d[kID] isEqualToString:ID]) { [filtered addObject:d]; } } NSLog(@"Count: %d", filtered.count); }];}- (void)testPerformanceEnumerationBlock { NSString *ID = @"204440e5-4069-48e8-a405-88882a5ba27e"; NSString *kID = @"id"; [self measureBlock:^{ NSMutableArray *filtered = [NSMutableArray array]; [self.largeListOfDictionaries enumerateObjectsUsingBlock:^(NSDictionary *d, NSUInteger idx, BOOL *stop) { if ([d[kID] isEqualToString:ID]) { [filtered addObject:d]; } }]; NSLog(@"Count: %d", filtered.count); }];}- (void)testPerformanceEnumerationConcurrent { NSString *ID = @"204440e5-4069-48e8-a405-88882a5ba27e"; NSString *kID = @"id"; [self measureBlock:^{ NSMutableArray *filtered = [NSMutableArray array]; [self.largeListOfDictionaries enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(NSDictionary *d, NSUInteger idx, BOOL *stop) { if ([d[kID] isEqualToString:ID]) { [filtered addObject:d]; } }]; NSLog(@"Count: %d", filtered.count); }];}
UPDATE
I changed the following in -testPerformanceEnumerationConcurrent
:
dispatch_sync(queue, ^{ [filtered addObject:d];});
And the results are still better for the concurrent version that in all other tests.
-[TestPMTests testPerformancePredicateWithFormat average: 0.134-[TestPMTests testPerformancePredicateWithBlock] average: 0.079-[TestPMTests testPerformanceEnumerationBlock] average: 0.079-[TestPMTests testPerformanceIndexesOfObjectsPassingTest] average: 0.068-[TestPMTests testPerformanceFastEnumeration] average: 0.054-[TestPMTests testPerformanceEnumerationConcurrent] average: 0.029