在特定位置避免GC收集以提高性能

3
我正在运行蒙特卡罗模拟。 工作被分配给许多不同的机器(通常约150台)。
每次迭代后,每个工作人员都会向服务器发送其结果。 在从所有工作人员获取结果后,服务器计算更新并将其发送回所有工作人员。
这个周期重复进行100-1000次。
服务器不能计算更新直到所有工作者都发送了他们的结果,所以如果99个工作者需要1秒钟完成一次迭代,而第100个工作者需要10秒钟,那么整个迭代就需要10秒钟。
问题在于GC会随机地在某些迭代的某些工作者上启动,从而导致这些工作者需要更长的时间,从而减缓整个过程的速度。
例如,在迭代#1期间,工人#58花费了10秒钟,而其他工人则花费了8秒钟。在迭代#2期间,不同的工作者需要更长的时间等等。
这增加的开销似乎约为20-30%。
我想做的是指示GC在迭代期间不执行任何收集。 只收集每10次迭代后(以便所有工作人员同步其收集),或在发送结果之后,在从服务器获取更新之前进行收集。
以下是我尝试做的伪代码:
public void Algorithm()
{
  for (var iteration = 0; iteration < 1000; iteration++)
  {
     PerformIteration(); //don't do any GC inside.
     SendResults();
     //Now there is a small time window to perform GC
     //before results from the server arrive (thats usually sub 0.5sec window)
     WaitForUpdate();
  }
}

设置:GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency有很大的帮助,但仍存在显着的开销。

每个工作线程都有244gb的内存,比模拟所需的要多得多。此外,几乎所有内容都被缓存,因此不需要进行Gen2收集。


你可以将关键部分用未管理的C++编写 - Chaka
你正在解决错误的问题。显然没有必要等待所有机器完成它们的工作,只需在一台机器传递其结果时分配另一台机器即可。 - Hans Passant
嗯,那听起来不太合理。在你的问题中描述一下如果只有50台机器可用你会怎么做。或者如果树的大小是三倍大,而你有150台机器,你会怎么做。 - Hans Passant
@HansPassant 想象一棵从根节点开始有 N 个子树的树(也就是说根节点有 N 个子节点)。现在有 M 个工作者。为了分配工作,每个工作者大约会得到 N/M 个子树来处理。现在一个常见的情况是有 338 个子树和 169 名工作者,这样每个工作者就能得到恰好 2 个子树。在这种情况下,如果我只使用了 168 名工作者,那么其中一个工作者就需要处理至少 3 个子树,从而使速度降低了 33%。这些子树通常大小相似。如果子树的大小增加了 3 倍,那么模拟将需要 3 倍的时间。 - Michal
让我们在聊天中继续这个讨论 - Michal
显示剩余3条评论
2个回答

8

.NET 4.6 引入了一个名为 GC.TryStartNoGCRegion 的新 GC 功能。

它告诉 GC 尝试在执行这段代码时不做任何垃圾回收:

尝试禁止垃圾回收器在关键路径执行期间进行垃圾回收,如果可用的内存量达到指定值,则控制垃圾回收器是否执行完整的阻塞式垃圾回收操作。

当你调用它时,你要告诉 GC 在执行 GC 之前你可以分配多少内存。它必须小于或等于临时段大小:

public void Algorithm()
{
   for (var iteration = 0; iteration < 1000; iteration++)
   {
        // allow the GC to allocate 4kb
        if (GC.TryStartNoGCRegion(4096, true))
        {
            try
            {
                PerformIteration();
                SendResults();
            }
            finally
            {
                GC.EndNoGCRegion();
            }
        }

      //Now there is a small time window to perform GC
      //before results from the server arrive (thats usually sub 0.5sec window)
      WaitForUpdate();
   }
}

尝试过类似的东西,但我认为我使用方法不对,GC.TryStartNoGCRegion导致应用程序挂起!我会尝试您提出的解决方案,看看它如何运作。 - Michal

0

-在C#中,您可以使用非托管代码,使用this (GC.AddMemoryPressure)

-或者使用托管的GC块。

GC.TryStartNoGCRegion(.)
{
   // your critical code
} GC.EndNoGCRegion()

*别忘了像之前的回答一样检查Try-condition

-GCSettings.LatencyMode看看MS手册

-在*.config文件中(应用程序或机器)更改GC配置,使用gcServergcConcurrent参数

<configuration>
    <runtime>
        <gcServer enabled="true"/>
<!-- OR / AND -->
        <gcConcurrent enabled="true|false"/>
    </runtime>
</configuration>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接