c# - 我可以 "prime"CLR GC 期望挥霍内存使用吗?

标签 c# .net memory-management garbage-collection

我们有一个服务器应用程序,它进行大量内存分配(短期和长期)。我们在启动后不久就看到了大量的 GC2 收集,但这些收集在一段时间后平静下来(即使内存分配模式是恒定的)。
这些集合很早就达到了性能。

我猜这可能是由 GC 预算引起的(对于 Gen2?)。有什么方法可以设置这个预算(直接或间接)来让我的服务器在开始时表现得更好?

我看到的一组违反直觉的结果:我们大大减少了内存(和大对象堆)分配的数量,从长远来看,性能有所提高,但早期性能变得更糟,并且“稳定下来” "时间变长了。

GC 显然需要一段时间才能意识到我们的应用程序是内存占用并相应地进行调整。我已经知道这个事实,我如何说服 GC?

编辑

  • 操作系统:64 位 Windows Server 2008 R2
  • 我们正在使用 .Net 4.0 ServerGC 批处理延迟。尝试了 4.5 和 3 种不同的延迟模式,虽然平均性能略有提高,但最坏情况下的性能实际上恶化了

  • 编辑 2
  • GC 峰值可以使从可接受到 Not Acceptable 时间加倍(我们说的是秒)
  • 几乎所有尖峰都与第 2 代集合相关
  • 我的测试运行导致最终的 32GB 堆大小。最初的泡沫持续运行时间的 1/5,之后的性能实际上更好(不太频繁的峰值),即使堆在增长。测试结束时的最后一个尖峰(具有最大的堆大小)与初始“训练”期间的 2 个尖峰(堆小得多)的高度相同(即一样糟糕)
  • 最佳答案

    在 .NET 中分配非常大的堆可能会非常快,并且阻塞集合的数量不会阻止它那么快。您观察到的问题是由以下事实引起的:您不仅分配,而且还有导致依赖项重组和实际垃圾收集的代码,所有这些都是在分配进行的同时。

    有几种技术需要考虑:

  • 尝试使用 LatencyMode ( http://msdn.microsoft.com/en-us/library/system.runtime.gcsettings.latencymode(v=vs.110).aspx ),在您主动加载数据时将其设置为 LowLatency - 另请参阅对此答案的评论
  • 使用多线程
  • 在主动加载时不要填充对新分配对象的交叉引用; 首先经过主动分配阶段,仅使用整数索引来交叉引用项,而不使用托管引用;然后强制 full GC 几次以在 Gen2 中包含所有内容,然后才填充您的高级数据结构;您可能需要重新考虑反序列化逻辑才能实现这一点
  • 尽可能早地将最大的根集合(对象数组、字符串)强制转换为第二代;在开始填充数据(加载数百万个小对象)之前,通过预先分配它们并强制执行两次 full GC 来做到这一点;如果您正在使用某种类型的通用字典,请确保尽早预分配其容量,以避免重组
  • 任何大的引用数组都是 GC 开销的一大来源——直到数组和引用对象都在 Gen2 中;数组越大 - 开销越大;更喜欢索引数组而不是引用数组,特别是对于临时处理需求
  • 避免释放或提升许多实用程序或临时对象 在任何线程上处于事件加载阶段时,请仔细查看您的代码中无法自动优化为“for”循环的字符串连接、装箱和“foreach”迭代器
  • 如果您有一个引用数组和一个函数调用层次结构,其中包含一些长时间运行的紧密循环,请避免引入从数组中的某个位置缓存引用值的局部变量;相反,缓存偏移值并在所有级别的函数调用中继续使用类似“myArrayOfObjects[offset]”的构造;它帮助我处理预填充的 Gen2 大型数据结构,我个人的理论是,这有助于 GC 管理对本地线程数据结构的临时依赖,从而提高并发性

  • 以下是这种行为的原因,据我所知,在应用程序启动期间使用多线程填充高达 ~100 Gb RAM:
  • 当 GC 将数据从一代移动到另一代时,它实际上是复制它并因此修改所有引用;因此,您在事件加载阶段的交叉引用越少 - 越好
  • GC 维护了很多管理引用的内部数据结构;如果您对引用本身进行大量修改 - 或者如果您在 GC 期间必须修改大量引用 - 它会在阻塞和并发 GC 期间导致显着的 CPU 和内存带宽开销;有时我观察到 GC 不断地消耗 30-80% 的 CPU 而不进行任何收集 - 只需进行一些处理,这看起来很奇怪,直到您意识到任何时候您将某个数组或某个临时变量的引用放入一个紧密循环中时,GC必须修改并有时重新组织依赖性跟踪数据结构
  • 服务器 GC 使用线程特定的 Gen0 段,并且能够将整个段推送到下一代(无需实际复制数据 - 虽然不确定这一点),请在设计多线程数据加载过程时记住这一点
  • ConcurrentDictionary 虽然是一个很棒的 AP​​I,但在具有多核的极端场景中无法很好地扩展,当对象数量超过几百万时(考虑使用针对并发插入优化的非托管哈希表,例如英特尔的 TBB 中的哈希表)
  • 如果可能或适用,请考虑使用 native 池分配器(再次英特尔 TBB)

  • 顺便说一句,.NET 4.5 的最新更新具有对大对象堆的碎片整理支持。升级到它的另一个重要原因。

    如果满足某些条件,.NET 4.6 也有一个 API 来要求不进行任何 GC (GC.TryStartNoGCRegion):https://msdn.microsoft.com/en-us/library/dn906202(v=vs.110).aspx

    另见 Maoni Stephens 的相关帖子:https://blogs.msdn.microsoft.com/maoni/2017/04/02/no-gcs-for-your-allocations/

    关于c# - 我可以 "prime"CLR GC 期望挥霍内存使用吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20215137/

    相关文章:

    iphone - 释放实例变量会导致崩溃,但为什么呢?

    c# - IsSingleByte Encoding的GetByteCount为什么要进行计算

    c# - WebAPI 中的 OData 没有 Entity Framework 之类的东西

    .net - 使用 SNMP 将文件从代理传输到管理器?

    c++ - 遍历 2D vector 以从内存中删除对象

    c++ - 为转发链链接新的运营商

    c# - 电源外壳 : Execution Policy

    c# - ASP.NET <%# 与 <%

    c# - .Net Machinekey.Protect - 使用什么算法?

    .net - 如何强制从哪里加载程序集?