.NET 4垃圾收集器的可伸缩性

我最近对.NET 4垃圾收集器进行了基准测试,从多个线程中集中分配。 当分配的值记录在一个数组中时,我没有像我预期的那样观察到没有可伸缩性(因为系统争用对共享旧代的同步访问)。 但是,当分配的值立即被丢弃时,我惊恐地发现没有可扩展性!

我曾预计临时案例几乎是线性扩展的,因为每个线程应该简单地擦除托儿所gen0干净并重新开始而不争用任何共享资源(没有任何东西幸存到老一代并且没有L2缓存未命中因为gen0很容易适合L1缓存)。

例如, 这篇MSDN文章说 :

无同步分配在多处理器系统上,托管堆的第0代将被分成多个内存竞技场,每个线程使用一个竞技场。 这允许多个线程同时进行分配,因此不需要对堆进行独占访问。

任何人都可以validation我的发现和/或解释我的预测和观察之间的这种差异吗?

这个问题不是一个完整的答案,而只是为了澄清一些误解:.NET GC只在工作站模式下并发。 在服务器模式下,它使用stop-the-world并行GC。 更多细节在这里 。 .NET中的单独托儿所主要是为了避免分配时的同步; 它们仍然是全局堆的一部分,不能单独收集。

不太确定这是什么以及你在机器上看到的确切内容。 但是,您的计算机上有两个不同版本的CLR。 Mscorwks.dll和mscorsvc.dll。 前者是您在工作站上运行程序时获得的程序,后者是Windows的某个服务器版本(如Windows 2003或2008)。

工作站版本对您的本地PC很友好,它不会吞噬所有机器资源。 在GC正在进行时,您仍然可以阅读您的电子邮件。 服务器版本经过优化,可在服务器级硬件上进行扩展。 大量的RAM(GC不会那么快)和大量的CPU核心(垃圾收集在多个核心上)。 你引用的文章可能会谈到服务器版本。

您可以在工作站上选择服务器版本,使用.config文件中的元素。

我可能会对发生的事情进行一些猜测。

(1)如果你有一个单独的线程并且在0代中有空闲空间,那么GC只会在分配了M个字节后运行。

(2)如果你有N个线程并且GC将第0代分成每个线程的N / M空间,那么每次线程分配N / M字节时GC都会运行。 这里的showstopper是GC需要“停止世界”(即挂起所有正在运行的线程),以便标记线程根集的引用。 这不便宜。 因此,GC不仅会更频繁地运行,而且会在每个集合上做更多的工作。

当然,另一个问题是multithreading应用程序通常不具有缓存友好性,这也会对性能产生重大影响。

我不认为这是一个.NET GC问题,而是GC的一般问题。 一位同事曾经运行过一个简单的“乒乓”基准测试,使用SOAP在两个线程之间发送简单的整数消息。 当两个线程处于不同的进程时,基准测试运行速度提高了两倍,因为内存分配和管理完全解耦!

非常快速,易于查看(直接根,分配空值)和大量发布可以让GC变得急切而且缓存本地堆的整个想法是一个很好的梦想:-)即使你有完全分离的线程局部堆(你不这样做)句柄指针表仍然必须是完全不稳定的,只是为了使一般的多CPU场景安全。 哦,记住有很multithreading,CPU缓存是共享的,内核需要优先,所以它不仅仅适合你:-)

还要注意带有双指针的“堆”有2个部分 – 要给出的内存块和句柄指针表(这样可以移动块但是代码总是有一个地址)。 这样的表是一个关键但非常精简的流程级资源,并且只是强调它的唯一方法是通过大量快速发布来泛滥它 – 所以你设法做到了:-))

一般来说,GC的规则是 – 泄漏:-)当然不是永远,但只要你能做到。 如果你还记得人们怎么说“不要强迫GCcollections”? 这是故事的一部分。 此外,“停止世界”集合实际上比“并发”更有效,并且过去通过更好的循环窃取或合作者合作来获知。 只有标记阶段需要冻结调度程序,并且在服务器上有几个线程正在执行它(N核心无论如何都是空闲的:-)另一个的唯一原因是它可以进行实时操作,比如播放video抖动就像更长的线程量子一样。

因此,如果您在短暂且频繁的CPU突发(小型alloc,几乎没有工作,快速发布)上与基础设施竞争,您将看到/测量的唯一事情将是GC和JIT噪声。

如果这是真实的东西,即不仅仅是试验,你可以做的最好的事情是在堆栈(结构)上使用大值数组。 它们不能强制进入堆并且像本地一样本地可以得到,并且不受任何后门移动=>缓存必须爱它们:-)这可能意味着切换到“不安全”模式,使用普通指针并且可能自己做一些alloc(如果yopu需要像列表一样简单的东西),但这是一个很小的代价来支付GC :-)试图强制数据进入缓存也取决于保持你的堆栈倾斜 – 记得你并不孤单。 同时为你的线程提供一些值得至少在发布版本之间运行的工作可能会有所帮助。 最糟糕的情况是,如果你在一个信号量子中分配和释放。

或解释我的预测和观察之间的这种差异?

基准测试很难。
对不完全控制的子系统进行基准测试更加困难。