objective ios objective-c nsarray filtering performance-testing

ios - objective - ¿Qué tiene índices de rendimiento más rápidosOfObjectsPassingTest o filteredArrayUsingPredicate?



nspredicate objective c (2)

Las siguientes pruebas (compiladas en modo Release, ejecutadas en Mac Pro) indican que filteredArrayUsingPredicate es más lento que indexesOfObjectsPassingTest si usa un predicado "textual", pero más rápido si usa un predicado basado en bloques. El método de ayuno en mi prueba fue un bucle simple (enumeración rápida) que agrega todos los objetos coincidentes a una matriz mutable.

Resultados para filtrar una matriz de 10,000,000 diccionarios, donde aproximadamente el 50% coincide con el predicado:

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

Por supuesto, los resultados pueden ser diferentes para otros predicados.

#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; }

Cuando se necesita filtrar un NSArray para obtener un subconjunto de los elementos en la matriz devuelta, ¿qué método es más rápido con mayor frecuencia y en casos límite?


Probé este problema con las nuevas pruebas de rendimiento Xcode 6 (Objective-C) con los casos de prueba a continuación. NSEnumerationConcurrent los siguientes resultados que indican que enumerationBlock con la bandera NSEnumerationConcurrent es el método de filtrado más rápido para matrices grandes:

testPerformancePredicateWithFormat - measured [Time, seconds] average: 0.189 testPerformancePredicateWithBlock - measured [Time, seconds] average: 0.093 testPerformanceEnumerationBlock - measured [Time, seconds] average: 0.092 testPerformanceIndexesOfObjectsPassingTest - measured [Time, seconds] average: 0.082 testPerformanceFastEnumeration - measured [Time, seconds] average: 0.068 testPerformanceEnumerationConcurrent - measured [Time, seconds] average: 0.036

Aquí las pruebas:

#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); }]; }

ACTUALIZAR

Cambié lo siguiente en -testPerformanceEnumerationConcurrent :

dispatch_sync(queue, ^{ [filtered addObject:d]; });

Y los resultados son aún mejores para la versión concurrente que en todas las otras pruebas.

-[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