6

I'm taking a college course about compilers and we just finished talking about garbage collection and ways to free memory. However, in class lectures and in our textbook, I was led to believe that reference counting was not a great way to manage memory.

The reasoning was that that reference counting is very expensive because the program has to insert numerous additional instructions to increment and decrement the reference count. Additionally, everytime the reference count changes, the program has to check if it equals zero and if so, reclaim the memory.

My textbook even has the sentence: "On the whole, the problems with reference counting outweight its advantages and it is rarely used for automatic storage management in programming language environments.

My questions are: Are these legitamate concerns? Does objective-c avoid them somehow? If so how?

4

3 回答 3

6

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

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

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

于 2013-05-01T18:23:17.077 回答
1

长期以来,计算机科学研究中关于 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 中。

于 2014-08-14T17:19:35.047 回答
0

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

棘手的词是automatic

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

现代自动引用计数为程序员做了很多事情,但它仍然不是透明的存储管理。程序员仍然必须了解引用计数,仍然必须解决引用循环问题。

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

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

于 2013-05-01T18:41:37.220 回答