【问题标题】:Parallel.ForEach Slower than ForEachParallel.ForEach 比 ForEach 慢
【发布时间】:2011-08-27 12:37:00
【问题描述】:

代码如下:

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 毫秒。我到底做错了什么,为什么它没有按我的预期工作?

【问题讨论】:

  • 基本上是因为涉及设置成本,并且您在循环内没有做足够的工作来证明开销是合理的。参见例如this answer。 (我希望这是一个重复的问题。)
  • Console.WriteLine() 使其完全无关紧要。
  • 尝试删除Console.WriteLine() 并用c.FirstName = c.FirstName.ToLowerInvariant() 替换它。如果您的收藏有大约 5000 件商品,您将不会看到差异;但是如果您的收藏有 6000、7000、... 10.000 件物品,在 4 核处理器上,您会看到很大的不同(Parallel.Foreach 会更快)

标签: c# .net parallel.foreach


【解决方案1】:

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

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

现在假设你有两万篇论文要评分,那么这将花费你大约 40000 分钟。现在,如果您花一个小时寻找助手,那将是一场胜利。你们每人拿了 4000 篇论文,总共用了 8060 分钟而不是 40000 分钟,几乎节省了 5 倍。寻找助手的开销基本上是无关紧要的。

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

进一步阅读:

Amdahl's law

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

Gustafson's law

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

【讨论】:

  • 在成为优秀的开发人员之前,你是一名优秀的作家。
【解决方案2】:

您应该意识到的第一件事是,并非所有并行性都是有益的。并行性有一定的开销,根据并行化的复杂性,这种开销可能很大也可能不显着。由于您的并行函数中的工作非常小,并行性必须执行的管理开销变得很大,从而减慢了整体工作。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

      正如之前的作者所说,Parallel.ForEach 会产生一些开销,但这并不是您看不到性能提升的原因。 Console.WriteLine 是同步操作,因此一次只有一个线程在工作。尝试将 body 更改为非阻塞的东西,您会看到性能提升(只要 body 中的工作量大到足以超过开销)。

      【讨论】:

      • 更准确地说:Console.WriteLine 是一个同步操作。
      【解决方案5】:

      我喜欢所罗门的回答,并想补充一点,您还有额外的开销

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-16
        • 1970-01-01
        相关资源
        最近更新 更多