首页 > 解决方案 > C# 并行 foreach 没有提供预期的加速

问题描述

我试图找出为什么并行 foreach 不能在具有 32 个物理内核和 64 个逻辑内核的机器上通过简单的测试计算给出预期的加速。

... 
var parameters = new List<string>();
for (int i = 1; i <= 9; i++) {
    parameters.Add(i.ToString());
    if (Scenario.UsesParallelForEach)
    {
        Parallel.ForEach(parameters, parameter => {
            FireOnParameterComputed(this, parameter, Thread.CurrentThread.ManagedThreadId, "started");
            var lc = new LongComputation();
            lc.Compute();
            FireOnParameterComputed(this, parameter, Thread.CurrentThread.ManagedThreadId, "stopped");
        });
    } 
    else
    {
        foreach (var parameter in parameters)
        {
            FireOnParameterComputed(this, parameter, Thread.CurrentThread.ManagedThreadId, "started");
            var lc = new LongComputation();
            lc.Compute();
            FireOnParameterComputed(this, parameter, Thread.CurrentThread.ManagedThreadId, "stopped");
        }
    }
}
...

class LongComputation
{
    public void Compute()
    {
        var s = "";
        for (int i = 0; i <= 40000; i++)
        {
            s = s + i.ToString() + "\n";
        }
    }
}

Compute 功能大约需要 5 秒才能完成。我的假设是,对于并行 foreach 循环,每次额外的迭代都会创建一个在其中一个内核上运行的并行线程,并且只需要计算一次 Compute 函数所需的时间。因此,如果我运行循环两次,然后使用顺序 foreach,则需要 10 秒,而并行 foreach 只需 5 秒(假设有 2 个内核可用)。加速比为 2。如果我运行循环 3 次,那么使用顺序 foreach 将需要 15 秒,但再次使用并行 foreach 只需 5 秒。加速比是 3,然后是 4、5、6、7、8 和 9。但是,我观察到的加速比恒定为 1.3。

顺序与并行 foreach。X 轴:计算的顺序/并行执行次数。Y轴:以秒为单位的时间

加速,顺序 foreach 的时间除以并行 foreach

FireOnParameterComputed 中触发的事件旨在用于 GUI 进度条以显示进度。在进度条中可以清楚地看到,对于每次迭代,都会创建一个新线程。

我的问题是,为什么我没有看到预期的加速或至少接近预期的加速?

标签: c#parallel.foreach

解决方案


任务不是线程。

有时启动任务会导致创建线程,但并非总是如此。创建和管理线程会消耗时间和系统资源。当一项任务只需要很短的时间时,即使它违反直觉,单线程模型通常也会更快。

CLR 知道这一点,并尝试根据许多因素(包括您传递给它的任何提示)对如何执行任务做出最佳判断。

对于 Parallel.ForEach,如果您确定要生成多个线程,请尝试传入 ParallelOptions。

Parallel.ForEach(parameters, new ParallelOptions { MaxDegreeOfParallelism = 100 }, parameter => {});

推荐阅读