12

所以我正在阅读这篇文章,试图从 Python 解释器中删除全局解释器锁 (GIL) 以提高多线程性能,并看到了一些有趣的东西。

事实证明,删除 GIL 实际上使事情变得更糟的地方之一是内存管理:

使用自由线程,引用计数操作失去了它们的线程安全性。因此,补丁引入了全局引用计数互斥锁以及用于更新计数的原子操作。在 Unix 上,锁定是使用标准 pthread_mutex_t 锁(包装在 PyMutex 结构中)和以下函数实现的......

...在 Unix 上,必须强调简单的引用计数操作已被不少于三个函数调用以及实际锁定的开销所取代。比它贵很多...

...显然,引用计数的细粒度锁定是导致性能不佳的主要原因,但即使您取消锁定,引用计数性能仍然对任何类型的额外开销(例如,函数调用等)非常敏感.)。在这种情况下,性能仍然是使用 GIL 的 Python 的两倍。

然后:

引用计数对于自由线程来说是一种非常糟糕的内存管理技术。这已经广为人知,但性能数据给出了一个更具体的数字。对于任何尝试 GIL 移除补丁的人来说,这绝对是最具挑战性的问题。

所以问题是,如果引用计数对于线程来说是如此糟糕,那么 Objective-C 是如何做到的呢?我已经编写了多线程 Objective-C 应用程序,并没有注意到内存管理的太多开销。他们在做别的事情吗?像某种对象锁而不是全局锁?Objective-C 的引用计数实际上在技术上对线程不安全吗?作为并发专家,我还不足以真正推测很多,但我有兴趣知道。

4

2 回答 2

11

存在开销,并且在极少数情况下(例如,微基准测试;)可能很重要,无论是否进行了优化(其中有很多)。但是,正常情况针对对象引用计数的非竞争性操作进行了优化。

所以问题是,如果引用计数对于线程来说是如此糟糕,那么 Objective-C 是如何做到的呢?

有多个锁在起作用,实际上,对任何给定对象的保留/释放会为该对象选择一个随机锁(但总是相同的锁)。因此,减少了锁争用,同时不需要每个对象一个锁。

(以及 Catfish_man 所说的;一些类将实现自己的引用计数方案,以使用特定于类的锁定原语来避免争用和/或针对其特定需求进行优化。)

实现细节更复杂。

Objectice-C 的引用计数实际上在技术上对线程不安全吗?

不——它在线程方面是安全的。

实际上,与其他操作相比,典型的代码会调用retain并且release非常罕见。因此,即使在这些代码路径上有很大的开销,它也会在应用程序中的所有其他操作中分摊(相比之下,将像素推送到屏幕确实很昂贵)。

如果一个对象是跨线程共享的(通常是坏主意),那么保护数据访问和操作的锁定开销通常会比保留/释放开销大得多,因为保留/释放的频率很低。


就 Python 的 GIL 开销而言,我敢打赌,它与作为正常解释器操作的一部分的引用计数增加和减少的频率有关。

于 2012-12-18T22:19:26.950 回答
10

除了 bbum 所说的之外,许多在 Cocoa 中最常被抛出的对象覆盖了正常的引用计数机制并在对象中存储了一个内联引用计数,它们使用原子加减指令而不是锁定来操作。

(从未来编辑:Objective-C 现在通过将 refcount 与“isa”指针混合,在现代 Apple 平台上自动进行此优化)

于 2012-12-18T22:35:19.800 回答