长期以来,计算机科学研究中关于 RC 与跟踪的共识是,尽管暂停时间较长(最大),但跟踪具有出色的 CPU 吞吐量。(例如,请参见此处、此处和此处。)直到最近,在 2013 年,才有一篇论文(这三个下的最后一个链接)提出了一个基于 RC 的系统,该系统的性能与经过最佳测试的跟踪 GC 相同或略好,具有关于 CPU 吞吐量。不用说它还没有“真正的”实现。
这是我刚刚在 iOS 7.1 64 位模拟器中使用 3.1 GHz i5 的 iMac 上做的一个小基准测试:
long tenmillion = 10000000;
NSTimeInterval t;
t = [NSDate timeIntervalSinceReferenceDate];
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:tenmillion];
for (long i = 0; i < tenmillion; ++i)
[arr addObject:[NSObject new]];
NSLog(@"%f seconds: Allocating ten million objects and putting them in an array.", [NSDate timeIntervalSinceReferenceDate] - t);
t = [NSDate timeIntervalSinceReferenceDate];
for (NSObject *obj in arr)
[self doNothingWith:obj]; // Can't be optimized out because it's a method call.
NSLog(@"%f seconds: Calling a method on an object ten million times.", [NSDate timeIntervalSinceReferenceDate] - t);
t = [NSDate timeIntervalSinceReferenceDate];
NSObject *o;
for (NSObject *obj in arr)
o = obj;
NSLog(@"%f seconds: Setting a pointer ten million times.", [NSDate timeIntervalSinceReferenceDate] - t);
禁用 ARC ( -fno-objc-arc
) 后,将给出以下结果:
2.029345 seconds: Allocating ten million objects and putting them in an array.
0.047976 seconds: Calling a method on an object ten million times.
0.006162 seconds: Setting a pointer ten million times.
启用 ARC 后,将变为:
1.794860 seconds: Allocating ten million objects and putting them in an array.
0.067440 seconds: Calling a method on an object ten million times.
0.788266 seconds: Setting a pointer ten million times.
显然分配对象和调用方法变得更便宜了。分配给一个对象指针变得更加昂贵了几个数量级,尽管不要忘记我没有在非 ARC 示例中调用 -retain,并且请注意,如果__unsafe_unretained
您有一个分配对象指针的热点,例如疯狂的。然而,如果您想“忘记”内存管理并让 ARC 在任何需要的地方插入保留/释放调用,那么在一般情况下,您将在所有设置指针的代码路径中反复浪费大量 CPU 周期。另一方面,跟踪 GC 让您的代码本身独立,并且只在特定时刻(通常在分配某些东西时)启动,一举完成它的事情。(当然细节很多实际上更复杂,考虑到分代 GC、增量 GC、并发 GC 等)
所以是的,由于 Objective-C 的 RC 使用原子保留/释放,它相当昂贵,但 Objective-C 也比引用计数所带来的效率低得多。(例如,方法的完全动态/反射性质,可以在运行时的任何时间“混搭”,阻止编译器进行许多需要数据流分析等的跨方法优化。一个 objc_msgSend( ) 总是从静态分析器的角度调用“动态链接”黑盒,可以这么说。)总而言之,Objective-C 作为一种语言并不是最有效或最优化的;人们称其为“C 的类型安全性和 Smalltalk 的超快速度”是有原因的。;-)
在编写 Objective-C 时,通常只使用实现良好的 Apple 库,这些库肯定使用 C 和 C++ 以及汇编或其他任何东西作为其热点。您自己的代码几乎不需要高效。当出现热点时,您可以通过在单个 Objective-C 方法中下降到较低级别的结构(例如纯 C 样式代码)来使其非常高效,但很少有人需要这样做。这就是为什么 Objective-C 在一般情况下可以负担得起 ARC 的成本。我还不相信跟踪 GC 有任何内在的内存受限环境中的问题,并认为可以使用适当的高级语言来检测所述库,但显然 RC 更适合 Apple/iOS。当问自己为什么不使用跟踪 GC 时,必须考虑他们迄今为止构建的整个框架以及所有遗留库;例如,我听说 RC 相当深入地构建在 CoreFoundation 中。