Mac OS X NSArray 枚举性能研究的示例分析
Mac OS X NSArray 枚举性能研究的示例分析,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。
一天,我在思考 NSArray 枚举方法 (也称迭代方法): Mac OS X 10.6 和 iOS 4 带来了以块(block)组成的美丽新世界,enumerateObjectsUsingBlock: 方法随之而来。我感觉这个方法要慢于快速枚举 (for (object in array) { ... }),因为有总体开销,但我并不能确定。因此我决定做一次性能测评。
都有哪些枚举方法?
总体来说,我们有4种可以使用的枚举方法 (参考 Mike Ash 的 周五常见问题 2010-04-09: Objective-C 的枚举方法对比)。
objectAtIndex: enumeration 使用一个 for 循环,递增循环变量,然后用 [myArray objectAtIndex:index] 来访问元素。这是最基本的枚举形式。
NSUInteger count = [myArray count]; for (NSUInteger index = 0; index < count ; index++) { [self doSomethingWith:[myArray objectAtIndex:index]]; }
2、NSEnumerator 外部迭代(external iteration)的形式: [myArray objectEnumerator] 返回一个对象,这个对象有 nextObject 方法。我们可以循环调用这个方法,直到返回 nil 为止。
NSEnumerator *enumerator = [myArray objectEnumerator]; id object; while (object = [enumerator nextObject]) { [self doSomethingWith:object]; }
3、NSFastEnumerator The idea behind 快速枚举 的思想是利用 C 数组快速访问 来优化迭代。不仅它理论上比传统的 NSEnumerator 更快,而且 Objective-C 2.0 提供了这种简明的语法:
id object; for (object in myArray) { [self doSomethingWith:object]; }
4、Block enumeration(块枚举)引入 blocks 后出现的方法,它可以基于块来迭代访问一个数组。它的语法没有快速枚举那么简洁,但它有一个有趣的特性: 并发枚举。如果枚举的顺序并不重要,而且实施的处理可以并发进行,不用锁,这种方法可以在多核系统上带来相当明显的效率提升。详情参考 并发枚举一节。
[myArray enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) { [self doSomethingWith:object]; }]; [myArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [self doSomethingWith:object]; }];
线性枚举
首先,我们讨论一下线性枚举:一个项目接着前一个。
图表
结论
有一点令人惊讶的是,NSEnumerator甚至比使用objectAtIndex:还慢。这对于Mac OS X 和IOS是一个事实。我猜想这是由于枚举器在每次迭代时都去检查数组是否被修改。自然地,快速枚举保存了每个原始的名字,因此是最快的解决方案。
对于小的数组,block enumeration 比objectAtIndex:稍慢一点,但在有大量元素的数组里,它的性能变得与fast enumeration差不多快。
fast enumeration和NSEnumeration之间的区别在很多地方已经非常明显:对于iPhone 4S,前者花费约0.037秒而后者需要0.140秒。这已经相差了3.7陪。
奇怪的一点
***在程序中分配 NSArray 和***用objectEnumerator 获取 enumerator 都需要异常长的时间才能完成。例如,在我 2007 年的 17 寸 MacBook Pro 上分配含一个元素的数组,所需时间的中位数是 415 纳秒。但***分配的时候会需要 500,000 纳秒,有时甚至要到 1,000,000 纳秒!获取 enumerator 也是如此:尽管中位数只有 673 纳秒,***获取却要花 500,000 纳秒以上。
我只能猜测其中的原因,但我怀疑延迟加载是罪魁祸首。在实际应用中,你可能不会注意到这一点,因为等到执行你的代码时,Cocoa 或 Cocoa Touch 很可能已经创建过数组了。
并发枚举
如果情况允许,你可以选择用块枚举来并发枚举对象。这意味着计算的工作量可以分散到几个 CPU 内核上。并不是每种枚举过程中的处理都是可并发的,因此只有没用到锁的时候,才能使用并发枚举:要么每一步操作确实是绝对相互独立的,要么有原子性的操作可用 (如 OSAtomicAdd32 之类)。
那么,它相比其他枚举类型有多大优势呢?
图表
结论
元素不多时,并发枚举是目前最慢的方法。主要原因可能是为了让数组能并发访问而做的准备工作和开启线程(我不知道用的是 GCD 还是“传统的”线程,这不重要;这是我们不需关心的实现细节)。
尽管如此,如果数组足够大,并发枚举突然就成了最快的方法了,正如我们所料。在 iPhone 4S 上枚举 100 万个元素,用并发枚举需要 0.024 秒,但快速枚举需要 0.036 秒。相形之下,还是同一个数组,NSEnumeration 要用 0.139 秒! 这已经是非常大的差距了,足有 5.7 倍之多。
在我的办公室,2011 iMac 24"采用了酷睿i7四核CPU,同时在0.0016秒之内列举了百万项。同一数组快速枚举了0.0044秒和NSEnumeration o.oo93秒。那个因数是5.8,它非常接近于ipone 4S的结果。在这里,我期待一个更大的差异,虽然,在我的2007 MacBook采用了Core2 Duo双核CPU,在这里因数刚好是3.7.当同时枚举的阈值成为有用,在某处以我的测试是10,000和50,000分子之间。用更少的分子元素,去掉正常的块迭代。
分配方式
我也想知道枚举的性能会不会受数组创建方式的影响。我测试了两个不同的方法:
首先创建一个 C 数组,里面引用了数组元素的对象实例,然后再用 initWithObjects:count: 创建NSArray。
直接创建 NSMutableArray 并依次用 addObject: 添加对象。
结果是迭代过程的没有区别,但分配过程有所不同:initWithObjects:count: 快一些。数组元素很多时,差距更加显著。这个例子创建了一个元素为 NSNumber 的数组:
NSArray *generateArrayMalloc(NSUInteger numEntries) { id *entries; NSArray *result; entries = malloc(sizeof(id) * numEntries); for (NSUInteger i = 0; i < numEntries; i++) { entries[i] = [NSNumber numberWithUnsignedInt:i]; } result = [NSArray arrayWithObjects:entries count:numEntries]; free(entries); return result; }
我是如何来测量的?
你可以从 http://darkdust.net/files/arraytest.m 来下载这个测试应用 看看我是如何来测量的。基本上我就是测量重复迭代一个数组(什么处理也不做)1000次需要多长时间。在图表中,取每个数组尺寸的平均值。这个应用的编译选项是关闭优化(-O0)。对于 iOS,我是在一个 iPhone 4S 上进行的测试。对 MAC OS X,我用我家里2007年产的 MacBook Pro 17”和我办公室2011年产的 iMac 24”来测试。MAC OS X的图表显示的是iMac上的结果,在MacBook Pro上的图表看起来与此相似,只是更慢一些。
关于Mac OS X NSArray 枚举性能研究的示例分析问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注编程网行业资讯频道了解更多相关知识。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341