indexOfObjectsPassingTest 或 filteredArrayUsingPredicate 哪个性能更快?

Posted

技术标签:

【中文标题】indexOfObjectsPassingTest 或 filteredArrayUsingPredicate 哪个性能更快?【英文标题】:Which has faster performance indexesOfObjectsPassingTest or filteredArrayUsingPredicate? 【发布时间】:2014-02-05 02:01:04 【问题描述】:

当需要过滤 NSArray 以获取返回的数组中项目的子集时,哪种方法更频繁且在边缘情况下更快?

【问题讨论】:

首先:profile(使用 Instruments)会告诉你区别。但老实说,如果在这种情况下性能对您的应用程序至关重要,那么您可能使用了错误的方法。 Objective-C 本身、NSPredicates、NSString 比较以及更多惯用的 Objective-C 实现并不是真正“快”(如果你需要的话)。 即使我们为您测量(或您为自己测量),您问题的答案也将取决于您打算如何处理结果。例如循环遍历 -indexesOfObjectsPassingTest: 返回的 NSIndexSet 的内容,每次调用 -objectAtIndex: 可能不是最有效的做法。 【参考方案1】:

以下测试(在发布模式下编译,在 Mac Pro 上执行)表明 filteredArrayUsingPredicateindexesOfObjectsPassingTest 慢如果你使用 “文本”谓词,但如果您使用基于块的谓词,则速度更快。 我的测试中的禁食方法是一个简单的(快速枚举)循环,它添加了所有匹配 对象到一个可变数组。

过滤包含 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;

【讨论】:

您可以尝试 indexOfObjectsWithOptions:NSEnumerationConcurrent passingTest: 使用多个处理器应该会更快。 @gnasher729:好建议。我已经做到了,有趣的是,indexesOfObjectsWithOptions:NSEnumerationConcurrent(在我的 4 核 Intel Core i5 iMac 上)比非并发版本慢 30%。 Instruments 显示所有工作线程在OSSpinLockSlow 上花费了大量时间(这是保护对结果索引集的访问所必需的)。 我使用indexesOfObjectsWithOptions:NSEnumerationConcurrent 对此进行了测试,在我的情况下,它甚至比使用快速枚举的测试还要快一些。由于即使您使用并发版本,结果索引也已经排序,因此应该将其用作选择的过滤方法。 ~ 使用 Xcode 6.2 beta 3 (6C101) 和 this MacBook Pro 测试 @blackjacx:奇怪。我再次测试了代码,并在我的filter3() 函数中将indexesOfObjectsPassingTest: 替换为indexesOfObjectsWithOptions:NSEnumerationConcurrent passingTest: 使它变慢了。 嗯,是的,这真的很奇怪。您可以使用我的代码进行测试。它在Github。【参考方案2】:

我用全新的 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

【讨论】:

请注意,NSMutableArray 不是线程安全的。因此,像在 testPerformanceEnumerationConcurrent 方法中那样从多个线程添加对象可能会导致错误的结果或崩溃。您将不得不使用一些锁定来保护可变数组免受来自不同线程的同时访问,这会使事情再次变慢。比较我上面的评论***.com/questions/21157109/…。 我自己查了一下,发现你的评论 Martin。好点。 testPerformanceEnumerationConcurrent 确实使我的应用程序因非线程安全而崩溃,如果我使用锁定和解锁,则性能与testPerformancePredicateWithFormat 相同,testPerformanceFastEnumeration 的性能最佳

以上是关于indexOfObjectsPassingTest 或 filteredArrayUsingPredicate 哪个性能更快?的主要内容,如果未能解决你的问题,请参考以下文章