Objective-C如何有效地引用计数?

时间:2013-05-01 18:18:39

标签: ios objective-c garbage-collection automatic-ref-counting reference-counting

我正在参加关于编译器的大学课程,我们刚刚讨论了垃圾收集和释放内存的方法。然而,在课堂讲座和我们的教科书中,我被引导相信引用计数不是管理记忆的好方法。

原因是引用计数非常昂贵,因为程序必须插入许多附加指令来递增和递减引用计数。此外,每次引用计数更改时,程序必须检查它是否等于零,如果是,则回收内存。

我的教科书甚至还有句子:“总的来说,引用计数的问题超重了它的优点,很少用于编程语言环境中的自动存储管理。

我的问题是:这些合法性问题是否存在? Objective-c是否以某种方式避免它们?如果是这样的话?

3 个答案:

答案 0 :(得分:6)

引用计数确实有有意义的开销,这是真的。然而,跟踪垃圾收集器的“经典教科书”解决方案也并非没有缺点。最大的一个是非确定性,但暂停和吞吐量也是一个重要的问题。

最后,ObjC并没有真正做出选择。最先进的复制收集器需要ObjC没有的语言的某些属性(例如没有原始指针)。因此,尝试将教科书解决方案应用于ObjC最终需要一个部分保守的非复制收集器,这实际上与引用计数的速度大致相同,但没有确定性行为。

(编辑)我的个人感受是吞吐量是次要的,甚至是第三级的,并且真正重要的争论归结为确定性行为与循环收集和通过复制的堆压缩。所有这三个都是如此有价值的属性,我很难选择一个。

答案 1 :(得分:1)

在很长一段时间内,关于RC与计算机科学研究中的跟踪的共识是,尽管有更长的(最大)暂停时间,但跟踪具有更高的CPU吞吐量。 (例如,请参阅hereherehere。)仅在最近,2013年,有一篇论文(这三篇文章中的最后一个链接)展示了基于RC的系统关于CPU吞吐量,或者比最佳测试的跟踪GC好一点。毋庸置疑,它还没有“真正的”实现。

这是我在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 ,并注意你可以使用{{1}如果你有一个热点,像疯了一样分配对象指针。然而,如果你想“忘记”内存管理并让ARC在任何需要的位置插入保留/释放调用,那么在一般情况下,你会反复浪费大量CPU周期,并且在设置指针的所有代码中都会浪费。另一方面,跟踪GC会让您的代码本身独立,并且只在特定时刻(通常在分配内容时)启动,一举做好准备。 (当然,根据世代GC,增量GC,并发GC等,细节是很多更复杂。)

所以是的,因为Objective-C的RC使用原子保留/释放,它相当昂贵,但Objective-C的效率低于refcounting所带来的效率低。 (例如,方法的完全动态/反射性质,可以在运行时随时“调配”,防止编译器进行许多需要数据流分析等的跨方法优化。一个objc_msgSend( )总是从静态分析器的角度调用“动态链接”的黑盒子,所以说。)总而言之,Objective-C作为一种语言并不是最有效或最优化的。人们称之为“具有Smalltalk炽热速度的C型安全”是有原因的。 ; - )

在编写Objective-C时,人们通常只是围绕着良好实现的Apple库工具,这些库肯定会使用C和C ++以及汇编或其他任何热点。您自己的代码几乎不需要高效。当存在热点时,您可以通过在单个Objective-C方法中下降到较低级别的构造(如纯C样式代码)来提高效率,但很少需要这样做。这就是为什么Objective-C在一般情况下可以承担ARC的成本。我还不相信跟踪GC在内存受限的环境中有任何固有的问题,并且认为可以使用正确的高级语言来检测所述库,但显然RC更好用苹果/ iOS版。人们必须考虑到目前为止他们构建的整个框架以及所有他们的遗留库,当他们问自己为什么没有使用跟踪GC时;例如,我听说RC在CoreFoundation中已经非常深入。

答案 2 :(得分:0)

  

总体而言,引用计数的问题超出了它的优势,很少用于编程语言环境中的自动存储管理

棘手的词是automatic

手动引用计数是传统的Obj-C方式,通过将它们委托给程序员来避免问题。程序员必须知道引用计数并手动添加retainrelease调用。如果他/她创建了参考周期,他/她有责任解决它。

现代自动引用计数为程序员做了很多事情,但它仍然不是透明的存储管理。程序员仍然需要了解引用计数,仍然需要解决引用周期。

真正棘手的是创建一个框架,通过引用计数透明地处理内存管理,也就是说,无需程序员知道它。这就是它不用于自动存储管理的原因。

额外指令造成的性能损失不是很大,通常并不重要。