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

How can I achieve maximum parallelism and utilize maximum CPU with Parallel.ForEach?

有一个 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 个内核上,它使用了 CPU 的 80%,而在 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() 中。我需要一种方法来检测导致某种排序的问题。

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

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

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

另一个不错的调试技巧是注释掉代码。一旦可伸缩性限制消失,您就会知道是什么代码导致的。