【问题标题】:Does allocating objects of the same size improve GC or "new" performance?分配相同大小的对象会提高 GC 或“新”性能吗?
【发布时间】:2012-01-29 20:51:35
【问题描述】:

假设我们必须创建许多字节数组类型的小对象。大小各不相同,但始终低于 1024 字节,例如 780,256,953....

如果我们总是只分配字节[1024],并且只使用所需的空间,它会随着时间的推移提高 operator new 或 GC 效率吗?

UPD:这是短暂的对象,用于解析二进制协议消息。

UPD:在这两种情况下,对象的数量是相同的,只是分配的大小发生了变化(随机与总是 1024)。

在 C++ 中,由于碎片和 C++ 的新性能,这很重要。但是在 C# 中......

【问题讨论】:

  • 你有没有分析过它自己看看?
  • 不,我希望得到快速的答复,而且它需要长时间的分析系统。
  • 由于对象是“短期对象,为解析二进制协议消息而创建”,您会考虑使用不安全代码和stackalloc 吗?看起来这正是它的目的:在需要高性能的情况下短期分配小型数组。
  • 在优化方面没有免费的午餐。分析是游戏的名称。

标签: c# garbage-collection new-operator


【解决方案1】:

如果我们总是只分配字节[1024],并且只使用所需的空间,它会随着时间的推移提高 operator new 或 GC 效率吗?

也许吧。您将不得不对其进行分析并查看。

我们在 Roslyn 编译器中分配语法树节点的方式非常有趣,我最终会写一篇关于它的博客文章。在那之前,与您的问题相关的是这个有趣的琐事。我们的分配模式通常涉及分配一个“底层”不可变节点(我们称之为“绿色”节点)和一个包装它的“外观”可变节点(我们称之为“红色”节点)。正如您可能想象的那样,我们经常会成对分配这些:绿色、红色、绿色、红色、绿色、红色。

绿色节点是持久的,因此寿命很长;立面是短暂的,因为它们在每次编辑时都会被​​丢弃。所以经常出现垃圾收集器有green/hole/green/hole/green/hole,然后green节点上移一代的情况。

我们的假设一直是缩小数据结构总能提高 GC 性能。更小的结构等于分配更少的内存,等于更少的收集压力,等于更少的收集,等于更高的性能,对吧?但我们通过 profiling 发现,在这种情况下使红色节点变小实际上会降低 GC 性能。 孔的特定大小会以某种奇怪的方式影响 GC;不是垃圾收集器内部的专家,我不知道为什么会这样。

那么,改变分配的大小是否可能会以某种不可预见的方式影响 GC? 是的,有可能。但是,首先,它不太可能,其次,在你真正尝试之前,不可能知道你是否处于这种情况在真实场景中并仔细测量 GC 性能

当然,您可能不会受到 GC 性能的限制。 Roslyn 做了如此多的小分配,因此我们调整影响 GC 的行为至关重要,但我们做了很多疯狂的小分配。绝大多数 .NET 程序不会像我们那样强调 GC。如果您是少数以有趣的方式强调 GC 的程序,那么就没有办法绕过它;你将不得不分析和收集经验数据,就像我们在 Roslyn 团队中所做的那样。

如果你不是少数,那么不要担心 GC 性能;您可能在其他地方遇到了更大的问题,您应该首先处理。

【讨论】:

  • 请问处理 GC 的团队为什么会发生这种情况?我真的很想听听 GC 内部专家的回答。主要是因为这种行为对我来说没有任何意义。
【解决方案2】:

并非如此,分配或清除字节数组只需要一条指令,无论其大小如何。 (我说的是你的情况。有例外)

您不应该担心垃圾收集的性能方面,除非您确定它是您的应用程序的瓶颈(即您创建了许多具有复杂关系的引用,然后很快就将其扔掉......而垃圾收藏很明显。)

要阅读有关 .NET GC 性能问题(在一个令人印象深刻的用例中)的知名(并且非常有用)网站的精彩故事,请参阅此博客。 http://samsaffron.com/archive/2011/10/28/in-managed-code-we-trust-our-recent-battles-with-the-net-garbage-collector ;)

但是关于 GC 最重要的一点是:永远不要在确定您有问题之前进行优化。因为如果你这样做,你可能会有一个。应用程序很复杂,GC 在运行时与它的每个部分进行交互。除了简单的案例,事先预测其行为和瓶颈似乎(在我看来)很困难。

【讨论】:

  • 著名的遗言:“你不应该担心性能”。你知道,说很难预测瓶颈(这是真的)和你应该专注于优化出现的瓶颈之间存在巨大的区别,然后说你 永远不要甚至考虑优化,直到为时已晚
  • +1 我确实认为这个建议非常可靠,我认为 -1 不合适
  • 您确定有时甚至在知道瓶颈之前就应该用 GC 优化来折磨自己吗?好的,我同意,你不应该在你的代码中做任何愚蠢的事情,你应该知道 GC 是如何操作来做出明智的算法选择的。但是,stackoverflow 用来加快速度的那种优化(在我提供的链接中)真的不应该事先考虑:它们解决了一个问题,但是是相当奇怪的模式。别忘了我没有说一般话。我谈到了 GC 优化。好的,我正在编辑它;)
【解决方案3】:

我认为 GC 不会成为问题和/或瓶颈。

分配不同大小的对象并以不同的顺序释放它们会导致堆内存碎片化。所以这就是分配相同大小的对象可能会派上用场的地方。

如果您确实分配/释放了很多,并且确实认为这是您的瓶颈,请尝试通过本地对象缓存重用对象。这可以提高性能,尤其是当它们是不实现大量逻辑的纯数据对象时。如果对象确实实现了很多逻辑并且需要更复杂的初始化(RAII 模式),我会放弃提高程序稳健性的性能...

马里奥

【讨论】:

    【解决方案4】:

    我也不认为只分配 1024 字节数组会提高 GC。 由于 GC 是确定性的,我也认为这不是你的问题。

    您可以通过在数组周围使用using {} 语句来影响 GC,以更快地释放内存(也许)。

    【讨论】:

    • using 语句根本不会影响 GC。
    • using {} 退出后,指向其中任何内容的指针将被删除,因此 GC 将清理没有其他指针指向的任何资源(来自 using {} 之外的对象或方法)
    • 不一定。 using 块退出后的唯一保证是 (1) 对象的 Dispose() 方法已被调用,以及 (2) 局部变量 不再在范围内。执行using(var foo = new MyDisposableObject()) { SomeOtherObject.StaticListOfObjects.Append(foo); } 之类的操作是完全合法的,然后foo 绝对不会更早被GC。 GC 不知道 C# 范围:当对象的最后一个引用超出范围时,不一定会被 GC,并且在对它的引用仍在时不一定会 not 得到 GC范围!
    【解决方案5】:

    new 速度很快,是 GC 导致问题。因此,这取决于您的阵列的寿命。

    如果他们只活很短的时间,我认为分配 1024 字节数组不会有任何改进。事实上,由于空间的浪费,这会给 GC 带来更大的压力,并且可能会降低性能。

    如果它们在您的应用程序的生命周期中存在,我会考虑分配一个大数组并为每个小数组使用它的块。您需要对此进行分析以查看它是否有帮助。

    【讨论】:

    • 关于分配的生命周期的好点,但这里的关键字绝对是'profile':)
    • 我看不出它如何给 GC 带来更大的压力。分配对象的数量是一样的...
    • @Boris 对象会被分配到有一定容量的Gen-0堆中。 GC 会在堆满时运行,因此分配的空间越多,堆满的越快,触发 GC 的速度就越快。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-10
    • 1970-01-01
    • 1970-01-01
    • 2013-09-27
    相关资源
    最近更新 更多