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 有时返回的值比预期的少