我可以"启动"CLR GC以期望使用挥霍的内存吗?

Rob*_*Rob 8 .net c# garbage-collection memory-management

我们有一个服务器应用程序,它可以进行大量内存分配(包括短期内存和长期内存).我们在启动后不久就看到了大量的GC2系列,但这些系列在一段时间后会冷静下来(即使内存分配模式不变).这些系列在早期就达到了性能.

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

我看到的一组反直觉的结果:我们大大减少了内存(和大对象堆)分配的数量,这使得长期性能得到改善,但早期性能变得更糟,并且"安定下来" "时间变长了.

GC显然需要一段时间来实现我们的应用程序是一个记忆猪并相应地适应.我已经知道这个事实,我如何说服GC?

编辑

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

EDIT2

  • GC峰值可以将从可接受到不可接受的时间(我们正在谈论的秒数)加倍
  • 几乎所有的峰值都与第2代集合相关
  • 我的测试运行导致最终的32GB堆大小.最初的泡沫持续时间为运行时间的第1个1/5,之后的性能实际上更好(峰值频率更低),即使堆正在增长.测试结束时的最后一个峰值(具有最大堆大小)与初始"训练"期间的峰值中的2个(即坏)相同(具有小得多的堆)

Rom*_*nin 6

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

有几种技巧需要考虑:

  • 尝试使用LatencyMode(http://msdn.microsoft.com/en-us/library/system.runtime.gcsettings.latencymode (v=vs.110 ) .aspx),在您主动加载数据时将其设置为LowLatency -看到这个答案的评论

  • 使用多个线程

  • 在主动加载时不要填充对新分配对象的交叉引用; 首先进入活动分配阶段,只使用整数索引来交叉引用项目,但不使用托管引用; 然后强制完整GC几次将Gen2中的所有内容都包含在内,然后填充高级数据结构; 你可能需要重新思考你的反序列化逻辑才能实现这一点

  • 尝试尽可能早地将最大的根集合(对象,字符串数组)强制转换为第二代; 在开始填充数据(加载数百万个小对象)之前,通过预先分配它们并强制完全GC两次来完成此操作; 如果您使用某种通用词典,请确保尽早预先分配其容量,以避免重组

  • 任何大量的引用都是GC开销的重要来源 - 直到数组和引用的对象都在Gen2中; 阵列越大 - 开销越大; 更喜欢索引数组到引用数组,特别是对于临时处理需求

  • 避免在任何线程上处于活动加载阶段时释放或提升许多实用程序或临时对象,仔细查看代码中的字符串连接,装箱和'foreach'迭代器,这些迭代器无法自动优化为'for'循环

  • 如果你有一个引用数组和一个具有一些长期运行的紧密循环的函数调用层次结构,请避免引入从数组中的某个位置缓存引用值的局部变量; 相反,缓存偏移值并在函数调用的所有级别上继续使用类似"myArrayOfObjects [offset]"的构造; 它帮助我处理预先填充的Gen2大数据结构,我的个人理论是,这有助于GC管理对本地线程数据结构的临时依赖,从而提高并发性

以下是这种行为的原因,据我所知,在应用启动期间,通过多线程填充~100 Gb RAM:

  • 当GC将数据从一代移动到另一代时,它实际上会复制它,从而修改所有引用; 因此,在活动加载阶段您使用的交叉引用越少越好

  • GC维护着许多管理引用的内部数据结构; 如果您对引用本身进行大量修改 - 或者如果您在GC期间有大量必须修改的引用 - 它会在阻塞和并发GC期间导致显着的CPU和内存带宽开销; 有时我观察到GC不断消耗30-80%的CPU而没有任何集合 - 简单地通过做一些处理,这看起来很奇怪,直到你意识到任何时候你在一个紧凑的循环中引用某个数组或一些临时变量,GC必须修改并有时重新组织依赖关系跟踪数据结构

  • 服务器GC使用特定于线程的Gen0段,并且能够将整个段推送到下一代(没有实际复制数据 - 虽然不确定这一点),在设计多线程数据加载过程时请记住这一点

  • ConcurrentDictionary虽然是一个优秀的API,但在具有多个内核的极端情况下,当对象数量超过几百万时(考虑使用针对并发插入优化的非托管哈希表,例如与英特尔TBB一起使用的)

  • 如果可能或适用,请考虑使用本机池分配器(英特尔TBB,再次)

BTW,.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/