8

我曾多次向 D 编程语言运行时的维护者建议内存分配器/垃圾收集器应该使用自旋锁而不是常规的操作系统临界区。这还没有真正流行起来。以下是我认为自旋锁会更好的原因:

  1. 至少在我所做的综合基准测试中,当内存分配器/GC 锁发生争用时,它比操作系统关键部分快几倍。编辑:根据经验,在单核环境中使用自旋锁甚至没有可测量的开销,可能是因为锁需要在内存分配器中保持如此短的时间。
  2. 内存分配和类似操作通常只占用时间片的一小部分,甚至是上下文切换所需时间的一小部分,这使得在争用情况下进行上下文切换变得很愚蠢。
  3. 无论如何,有问题的实现中的垃圾收集会阻止世界。收集期间不会有任何旋转。

是否有充分的理由不在内存分配器/垃圾收集器实现中使用自旋锁?

4

6 回答 6

3
  1. 显然,自旋锁的最坏情况是可怕的(操作系统调度程序只看到 30 个受 CPU 限制的线程,因此它试图给它们全部一些 CPU 时间;其中 29 个在持有锁的线程休眠时疯狂地旋转),所以如果可以的话,你应该避免它们。很多比我聪明的人声称自旋锁因此没有用户空间用例。

  2. 系统互斥锁应该在使线程进入睡眠状态(或确实进行任何类型的系统调用)之前旋转一点,因此即使存在争用,它们有时也可以执行与自旋锁完全相同的操作。

  3. 分配器通常可以通过仅使用锁将页面分配给线程来实际消除锁争用。然后每个线程负责对自己的页面进行分区。您最终每 N 个分配只获取一次锁,您可以将 N 配置为您喜欢的任何内容。

我认为 2 和 3 是综合基准无法有效反驳的有力论据。您需要证明实际程序的性能会受到影响。

于 2009-12-15T22:05:07.773 回答
2

自旋锁在只有一个 CPU/核心的系统上绝对没有价值,或者 - 更一般地 - 在高争用情况下(当您有许多线程在锁上等待时)。

于 2009-12-15T21:49:52.890 回答
2

无论如何,在 Windows 上,临界区对象已经可以选择这样做(http://msdn.microsoft.com/en-us/library/ms682530.aspx):

线程使用 InitializeCriticalSectionAndSpinCount 或 SetCriticalSectionSpinCount 函数来指定临界区对象的自旋计数。自旋是指当一个线程试图获取一个被锁定的临界区时,线程进入一个循环,检查锁是否被释放,如果锁没有被释放,则线程进入睡眠状态。在单处理器系统上,自旋计数被忽略,临界区自旋计数设置为 0(零)。在多处理器系统上,如果临界区不可用,则调用线程在对与临界区关联的信号量执行等待操作之前旋转 dwSpinCount 次。如果在自旋操作期间临界区空闲,则调用线程避免等待操作。

希望其他平台如果还没有效仿的话。

于 2009-12-15T21:56:55.583 回答
2

是否有充分的理由不在内存分配器/垃圾收集器实现中使用自旋锁?

当一些线程是计算绑定(CPU绑定)而其他线程是内存分配器绑定时,使用自旋锁会占用 CPU 周期,否则这些 CPU 周期可能会被计算绑定线程和/或属于其他线程的线程使用过程。

于 2009-12-15T22:12:24.030 回答
0

不知道我是否同意,因为内存分配可能需要很长时间(如果您预先分配所有内存然后重新发出它,唯一的方法就是不这样做)..您确实需要尝试使用多个 gig 堆大小进行相同的分配和解除分配有数百万个条目,许多应用程序达到了分配关键部分(注意应用程序而不是线程),并且由于内存不足而导致磁盘垃圾/交换。您还可以在分配期间遇到磁盘交换问题,并且执行自旋锁等待磁盘请求肯定是不合适的。

正如 Cyber​​Shadow 在单线程 CPU 上提到的那样,您最终会进入一个带有开销的普通锁。现在,一种语言可以在许多都是单线程的嵌入式 CPU 上运行。

此外,如果您可以通过互锁交换逃脱,那是最好的(因为它是无锁的,尽管仍然会停止 CPU 并为多核内存提高 LOCK#),但大多数锁仍然使用它(但需要做更多)。然而,堆的结构通常意味着互锁的交换是不够的,你最终会创建一个临界区。请注意,在具有 GC 的(世代)标记扫描 Nursery 中,可以将分配作为指针的互锁比较和添加。我为 Cosmos C# OS GC 执行此操作,它用于堆栈速度分配。

于 2010-01-10T08:30:31.217 回答
0

Glasgow Haskell 编译器的垃圾收集器中的一个性能错误非常烦人,以至于它有一个名字,“最后一个核心减速”。这是他们在 GC 中不恰当地使用自旋锁的直接后果,并且在 Linux 上由于其调度程序而加剧,但事实上,只要其他程序竞争 CPU 时间,就可以观察到这种影响。

在第二张图上效果很明显,可以看出影响的不仅仅是这里的最后一个核心Haskell程序看到的性能下降超过了 5 个核心。

于 2010-06-11T23:54:42.277 回答