2013-07-10 65 views
12

我最近一直在关于连续或随机访问与C数组有关的NSArray的性能的一个小研究项目。大多数测试用例都是按照我的预期显示出来的,然而有些测试用例并不工作,我以为他们会这样做,我希望有人能够解释为什么。NSArray vs C Array性能比较

基本上,测试包括用50k对象填充一个C数组,迭代每个对象并调用一个方法(内部只增加对象中的一个浮点数),测试的第二部分涉及创建一个完成50k迭代,但访问数组中的随机对象。基本上它非常简单。

为了做比较,我用C数组初始化NSArray。然后每个测试都通过一个传入一个方法的块来运行,该方法跟踪执行该块所需的时间。我使用的代码包含在下面,但我想覆盖结果和我有第一个问题。

这些测试在iPhone 4上运行,并包装在dispatch_after中,以缓解由于启动应用程序而导致的任何剩余的线程或非原子操作。单次运行的结果如下,每次运行基本上是与微小变化是相同的:

===SEQUENCE=== 
NSARRAY FAST ENUMERATION: 12ms 
NSARRAY FAST ENUMERATION WEAK: 186ms 
NSARRAY BLOCK ENUMERATION: 31ms (258.3%) 
C ARRAY DIRECT: 7ms (58.3%) 
C ARRAY VARIABLE ASSIGN: 33ms (275.0%) 
C ARRAY VARIABLE ASSIGN WEAK: 200ms (1666.7%) 

===RANDOM=== 
NSARRAY RANDOM: 102ms (850.0%) *Relative to fast enumeration 
C ARRAY DIRECT RANDOM: 39ms (38.2%) *Relative to NSArray Random 
C ARRAY VARIABLE ASSIGN RANDOM: 82ms (80.4%) 

最快方法似乎使用直接访问在C Array中项目“*(CARRAY + IDX)” ,最令人费解的是,将C数组的指针分配给一个目标c变量“id object = *(carry + idx)”导致了巨大的性能下降。

我初步认为它可能是做引用计数的东西,因为变量很强,所以在这一点上,我将其改变为弱预期性能增加“__weak id object = *(carry + idx)”。令我惊讶的是它实际上慢了很多。

随机存取结果非常好,我基于序列结果的预期,所以没有足够幸运的意外。

由于这一结果有一些问题:

  1. 为什么赋值给一个变量这么长的时间?
  2. 为什么分配给弱变量的时间更长? (也许有什么我不明白在这里)
  3. 考虑到上述如何苹果得到了标准的快速枚举执行如此之好?

为了完整起见,这里是代码。所以我创建数组如下:

__block id __strong *cArrayData = (id __strong *)malloc(sizeof(id) * ITEM_COUNT); 

for (NSUInteger idx = 0; idx < ITEM_COUNT; idx ++) { 
    NSTestObject *object = [[NSTestObject alloc] init]; 
    cArrayData[idx] = object; 
} 

__block NSArray *arrayData = [NSArray arrayWithObjects:cArrayData count:ITEM_COUNT]; 

而且NSTestObject的定义是这样的:

@interface NSTestObject : NSObject 

- (void)doSomething; 

@end 

@implementation NSTestObject 
{ 
    float f; 
} 

- (void)doSomething 
{ 
    f++; 
} 

,并用于分析代码的方法:

int machTimeToMS(uint64_t machTime) 
{ 
    const int64_t kOneMillion = 1000 * 1000; 
    static mach_timebase_info_data_t s_timebase_info; 

    if (s_timebase_info.denom == 0) { 
     (void) mach_timebase_info(&s_timebase_info); 
    } 
    return (int)((machTime * s_timebase_info.numer)/(kOneMillion * s_timebase_info.denom)); 
} 

- (int)profile:(dispatch_block_t)call name:(NSString *)name benchmark:(int)benchmark 
{ 

    uint64_t startTime, stopTime; 
    startTime = mach_absolute_time(); 

    call(); 

    stopTime = mach_absolute_time(); 

    int duration = machTimeToMS(stopTime - startTime); 

    if (benchmark > 0) { 
     NSLog(@"%@: %i (%0.1f%%)", name, duration, ((float)duration/(float)benchmark) * 100.0f); 
    } else { 
     NSLog(@"%@: %i", name, duration); 
    } 

    return duration; 

} 

最后,这是我如何执行实际测试:

int benchmark = [self profile:^ { 
    for (NSTestObject *view in arrayData) { 
     [view doSomething]; 
    } 
} name:@"NSARRAY FAST ENUMERATION" benchmark:0]; 

[self profile:^ { 
    for (NSTestObject __weak *view in arrayData) { 
     [view doSomething]; 
    } 
} name:@"NSARRAY FAST ENUMERATION WEAK" benchmark:0]; 

[self profile:^ { 
    [arrayData enumerateObjectsUsingBlock:^(NSTestObject *view, NSUInteger idx, BOOL *stop) { 
     [view doSomething]; 
    }]; 
} name:@"NSARRAY BLOCK ENUMERATION" benchmark:benchmark]; 

[self profile:^ { 
    for (NSUInteger idx = 0; idx < ITEM_COUNT; idx ++) { 
     [*(cArrayData + idx) doSomething]; 
    } 
} name:@"C ARRAY DIRECT" benchmark:benchmark]; 

[self profile:^ { 
    id object = nil; 
    NSUInteger idx = 0; 
    while (idx < ITEM_COUNT) { 
     object = (id)*(cArrayData + idx); 
     [object doSomething]; 
     object = nil; 
     idx++; 
    } 
} name:@"C ARRAY VARIABLE ASSIGN" benchmark:benchmark]; 

[self profile:^ { 
    __weak id object = nil; 
    NSUInteger idx = 0; 
    while (idx < ITEM_COUNT) { 
     object = (id)*(cArrayData + idx); 
     [object doSomething]; 
     object = nil; 
     idx++; 
    } 
} name:@"C ARRAY VARIABLE ASSIGN WEAK" benchmark:benchmark]; 

NSLog(@"\n===RANDOM===\n"); 

benchmark = [self profile:^ { 
    id object = nil; 
    for (NSUInteger idx = 0; idx < ITEM_COUNT; idx ++) { 
     object = arrayData[arc4random()%ITEM_COUNT]; 
     [object doSomething]; 
    } 
} name:@"NSARRAY RANDOM" benchmark:benchmark]; 

[self profile:^ { 
    NSUInteger idx = 1; 
    while (idx < ITEM_COUNT) { 
     [*(cArrayData + arc4random()%ITEM_COUNT) doSomething]; 
     idx++; 
    } 
} name:@"C ARRAY DIRECT RANDOM" benchmark:benchmark]; 

[self profile:^ { 
    id object = nil; 
    NSUInteger idx = 0; 
    while (idx < ITEM_COUNT) { 
     object = (id)*(cArrayData + arc4random()%ITEM_COUNT); 
     [object doSomething]; 
     idx++; 
    } 
} name:@"C ARRAY VARIABLE ASSIGN RANDOM" benchmark:benchmark]; 
+1

必需阅读:[Ridiculous Fish:Array](http://ridiculousfish.com/blog/posts/array.html) – Caleb

回答

6

为什么分配给一个变量需要这么长时间?

你的猜测是正确的:当你将ARC调用retain,并release当你重新分配,或当id超出范围。

为什么分配给弱变量的时间更长? (也许这里有一些我不明白的事情发生在这里)

回想一下,ARC承诺在最后的强参考消失时清除弱参考。这就是为什么弱引用更昂贵的原因:为了nil out __weak id,ARC在运行时注册了id的地址,以获得被释放对象的通知。此注册需要写入哈希表 - 远远低于保留和释放。

考虑到以上情况,Apple如何获得标准快速枚举以表现如此出色呢?

快速枚举使用直接支持NSArray的数组块。实质上,他们抓取一个由30个左右的元素组成的块,并将它作为一个普通的C数组进行访问。然后他们抓住下一个块,像对待C数组一样遍历它,等等。有一些小的开销,但它是每块,而不是每个元素,所以你会得到一个非常令人印象深刻的性能。

+0

感谢您的回复I实际上确实试图抓取循环中多达10个元素的块并分配它们,但对性能几乎没有影响,因为大部分循环时间(〜80%)都用于变量赋值。我现在唯一可以想到的解释是,快速枚举保留了每个对象块,并在运行下一个块时以某种方式将它们全部释放到后台线程中。但我无法支持这一点。 – Andy

+3

@Andy我不认为快速枚举保留和释放对象:我认为他们使用'__unsafe_unretained',因为该对象已经由数组拥有。 – dasblinkenlight

+0

你先生是我的英雄。将__weak快速更改为__unsafe_unretained,性能现在是8ms,然后是NSArray。我没有意识到使用__unsafe_unretained会带来这样的性能优势,但正如你所说的,__weak引用必须将其值从零范围移除时设置为零,因此它的确有意义。非常感谢您的帮助! – Andy

0

2)因为你没有优化的可能性。静态的一块内存中包含的弱变量和进入静态的时间长于动态