如何通过 Parallel.ForEach 实现最大并行度并利用最大 CPU?

Posted

技术标签:

【中文标题】如何通过 Parallel.ForEach 实现最大并行度并利用最大 CPU?【英文标题】:How can I achieve maximum parallelism and utilize maximum CPU with Parallel.ForEach? 【发布时间】:2016-11-09 15:38:31 【问题描述】:

有一个 C# 函数 A(arg1, arg2) 需要多次调用。为了最快地做到这一点,我正在使用并行编程。

以以下代码为例:

long totalCalls = 2000000;
int threads = Environment.ProcessorCount;

ParallelOptions options = new ParallelOptions(); 
options.MaxDegreeOfParallelism = threads;

Parallel.ForEach(Enumerable.Range(1, threads), options, range =>

    for (int i = 0; i < total / threads; i++)
    
        // init arg1 and arg2
        var value = A(arg1, agr2);
        // do something with value
    
);

现在的问题是,这并没有随着内核数量的增加而扩大;例如在 8 核上它使用 80% 的 CPU,在 16 核上它使用 40-50% 的 CPU。我想最大限度地使用CPU。

您可以假设A(arg1, arg2) 内部包含一个复杂的计算,但它没有任何 IO 或网络绑定操作,也没有线程锁定。还有哪些其他方法可以找出代码的哪一部分使其无法以 100% 并行方式执行?

我也试过增加并行度,例如

int threads = Environment.ProcessorCount * 2;
// AND
int threads = Environment.ProcessorCount * 4;
// etc.

但这没有任何帮助。

更新 1 - 如果我通过将 A() 替换为计算素数的简单函数来运行相同的代码,那么它使用了 100 个 CPU 并且可以很好地扩展。所以这证明了另一段代码是正确的。现在问题可能在原始函数A() 内。我需要一种方法来检测导致某种排序的问题。

【问题讨论】:

使用Tasks 而不是Parallel.ForEach 会不会更好?然后,您可以更好地控制任务及其数量。 哪个操作系统?您是否正在运行没有 vshost.exe 的发布版本?当您测量 CPU 使用率时,该进程是唯一运行的进程吗?进程优先级? @dlatikay - Windows Server 2012。是的,我正在运行发布版本,除了默认操作系统功能之外,这是唯一正在运行的程序。我没有设置优先级。让我也试试。 @Liam Tasks API(Task 或 Parallel.For(Each))实现线程池。因此,使用MaxDegreeOfParallelism 不应该与使用多个任务一样有效吗? @Liam - 会尝试并更新结果。 【参考方案1】:

您已确定A 中的代码是问题所在。

有一个很常见的问题:垃圾收集。在app.config 中配置您的应用程序以使用并发服务器GC。 Workstation GC 倾向于序列化执行。效果很严重。

如果这不是问题,请暂停调试器几次并查看Debug -&gt; Parallel Stacks 窗口。在那里,你可以看到你的线程在做什么。寻找共同的资源和争用。例如,如果您发现许多线程在等待锁定,这就是您的问题。

另一种很好的调试技术是注释掉代码。一旦可扩展性限制消失,您就会知道是什么代码导致了它。

【讨论】:

我尝试设置并发服务器GC,但还没有成功。没有锁定A() 你尝试过调试器技术吗? 正在尝试,但到目前为止没有成功,由于某种原因,我无法分享 A() 包含的内容。 那么当你尝试它时发生了什么?你看见什么了?线程在做什么? 终于成功了。早些时候我试过&lt;gcConcurrent enabled="true"/&gt;,但没有效果。然后我尝试设置 '' 并且效果很好。非常感谢@usr!

以上是关于如何通过 Parallel.ForEach 实现最大并行度并利用最大 CPU?的主要内容,如果未能解决你的问题,请参考以下文章

如何限制 Parallel.ForEach?

如何将此 foreach 代码转换为 Parallel.ForEach?

计算 Parallel.ForEach 使用的线程数

如何从 Parallel.ForEach 收集返回值?

我在 Parallel.ForEach 循环中收到 TaskCanceledException,如何解决?

何时使用 Parallel.ForEach,何时使用 PLINQ