返された配列内のアイテムのサブセットを取得するためにNSArrayをフィルタリングする必要がある場合、どのメソッドがより頻繁に、エッジの場合に高速になりますか?
次のテスト(リリースモードでコンパイル、Mac Proで実行)indicate「テキスト」述語を使用する場合、filteredArrayUsingPredicate
はindexesOfObjectsPassingTest
よりも低速ですが、高速ですブロックベースの述語を使用する場合。私のテストでのfastedメソッドは、一致するすべてのオブジェクトを可変配列に追加する単純な(高速列挙)ループでした。
10,000,000の辞書の配列をフィルタリングした結果。ここで、約50%が述語に一致します。
8.514334(predicateWithFormat) 4.422550(predicateWithBlock) 5.170086(indexesOfObjectsPassingTest) 3.154015(高速列挙+可変配列)
もちろん、他の述語では結果が異なる場合があります。
#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;
}
この問題は、以下のテストケースを使用して、新しいXcode 6パフォーマンステスト(Objective-C)でテストしました。次の結果が得られました。これは、フラグNSEnumerationConcurrent
が付いたenumerationBlockが、大きな配列の最速のフィルタリング方法であることを示しています。
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
ここでテスト:
#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);
}];
}
-testPerformanceEnumerationConcurrent
で以下を変更しました。
dispatch_sync(queue, ^{
[filtered addObject:d];
});
そして、結果は、他のすべてのテストよりも並行バージョンの方が優れています。
-[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