Parallel.ForEach slower than foreach

本文关键字:foreach than slower ForEach Parallel | 更新日期: 2023-09-27 17:56:50

这是代码:

using (var context = new AventureWorksDataContext())
{
    IEnumerable<Customer> _customerQuery = from c in context.Customers
                                           where c.FirstName.StartsWith("A")
                                           select c;
    var watch = new Stopwatch();
    watch.Start();
    var result = Parallel.ForEach(_customerQuery, c => Console.WriteLine(c.FirstName));
    watch.Stop();
    Debug.WriteLine(watch.ElapsedMilliseconds);
    watch = new Stopwatch();
    watch.Start();
    foreach (var customer in _customerQuery)
    {
        Console.WriteLine(customer.FirstName);
    }
    watch.Stop();
    Debug.WriteLine(watch.ElapsedMilliseconds);
}

问题是,Parallel.ForEach大约需要 400 毫秒,而普通foreach大约需要 40 毫秒。我到底做错了什么,为什么这没有像我预期的那样工作?

Parallel.ForEach slower than foreach

假设您有一个任务要执行。假设你是一名数学老师,你有二十张论文要评分。批改一篇论文需要两分钟,所以大约需要四十分钟。

现在让我们假设您决定聘请一些助手来帮助您批改论文。找到四个助手需要一个小时。你们每人拿四张纸,八分钟就完成了。你已经用 40 分钟的工作换取了总共 68 分钟的工作,包括寻找助手的额外小时,所以这不是节省。寻找助手的开销大于自己完成工作的成本。

现在假设你有两万张论文要评分,所以大约需要 40000 分钟。现在,如果你花一个小时寻找助手,那就是一场胜利。你们每人拿4000张论文,总共用完8060分钟,而不是40000分钟,节省近5倍。 寻找助手的开销基本上无关紧要。

并行化不是免费的。与每个线程完成的工作量相比,在不同线程之间分配工作的成本需要很小。

延伸阅读:

阿姆达尔定律

给出在固定工作负载下执行任务的延迟的理论加速,这是资源得到改善的系统可以预期的。

古斯塔夫森定律

给出在固定执行时间执行任务的延迟的理论加速,这是资源得到改善的系统可以预期的。

您应该意识到的第一件事是,并非所有并行性都是有益的。 并行性存在一定数量的开销,这种开销可能很大,也可能不重要,具体取决于并行化的复杂性。 由于并行功能中的工作非常小,因此并行性必须执行的管理开销变得很大,从而减慢了整体工作的速度。

为可枚举 VS 创建所有线程的额外开销很可能是导致速度变慢的原因。 Parallel.ForEach不是一揽子提高性能的举措;需要权衡要为每个元素完成的操作是否可能阻塞。

例如,如果您要发出 Web 请求或其他内容,而不是简单地写入控制台,则并行版本可能会更快。实际上,简单地写入控制台是一项非常快的操作,因此创建线程和启动线程的开销会变慢。

正如前面的作者所说,有一些与Parallel.ForEach相关的开销,但这并不是你看不到性能改进的原因。 Console.WriteLine是同步操作,因此一次只有一个线程在工作。尝试将主体更改为非阻塞,您将看到性能提高(只要身体中的工作量足以超过开销)。

我喜欢萨洛蒙斯的回答,并想补充一点,你还有额外的开销

  1. 分配代表。
  2. 通过他们呼唤。

您也可以使用分区程序,以便将任务分解为大小分区,以避免创建许多任务的开销。

如何:加速小型环形体

调整 Partitioner.Create 的第三个参数以确定分区的大小可以帮助您获得更好的性能。就我而言,我尝试将其设置为 2 个分区(分区大小 = (总元素数/2) + 1),并且比用于简单任务的 foreach 循环获得了略好的性能(好 10%)。

请记住,对于非常简单的任务,例如在您的案例中,这可能没有多大帮助,并且您的表现可能低于使用简单的foreach,正如先前的Anwers指出的原因一样。