Parallel.ForEach 比 ForEach 慢

Posted

技术标签:

【中文标题】Parallel.ForEach 比 ForEach 慢【英文标题】:Parallel.ForEach Slower than 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 会更快) 【参考方案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】:

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

    分配代表。 通过他们呼叫。

【讨论】:

以上是关于Parallel.ForEach 比 ForEach 慢的主要内容,如果未能解决你的问题,请参考以下文章

为什么线程比Parallel.Foreach更快打开OracleConnection?

C# Parallel.ForEach 和 Task.WhenAll 有时返回的值比预期的少

C# - 用于服务调用的 Parallel.Foreach()

如何限制 Parallel.ForEach?

计算 Parallel.ForEach 使用的线程数

打破parallel.foreach?