我一直在尝试优化我的WPF应用程序的性能,其中将从服务器下载几乎 40K联系人,并且将在应用程序中创建完整的分层树。用户只能从视图中的树中搜索。现有代码使用了大量 foreach 循环,这需要花费大量时间(创建视图时只需5-6秒);所以我想修改代码,我做的第一件事就是用 Paralle.ForEach 替换 foreach 循环。我创建了一个非常非常的示例应用程序来测试两者之间的差异。
Parallel.ForEach
Parallel.ForEach(lstUsers, item =>
{
Console.WriteLine(item.ToString());
});
正常的foreach
foreach (var item in lstUsers)
{
Console.WriteLine(item.ToString());
}
我针对1000个联系人运行了应用程序,令我惊讶的是结果如下:
Parallel.ForEach - > 210毫秒
foreach - > 104毫秒
我认为 Parallel.ForEach 应该比 foreach 循环花费更少的时间,但我看到正好相反。看起来我完全错了。
答案 0 :(得分:1)
您证明此算法(Console.Writeline)不适合并行化。在多个线程上运行它的开销(这不是微不足道的)不能通过并行运行节省的时间来赢回
答案 1 :(得分:0)
我强烈建议您下载并阅读Microsoft" Patterns for Parallel Programming"的免费书籍。你正在做的是他们在"反模式"中所涵盖的一个例子。部分(人们倾向于做但却不好的模式)
非常小的环体
如前所述,
Parallel
类在a中实现 方式以便在产生的同时提供高质量的负载平衡 尽可能少开销。但是仍有开销。该Parallel.For
产生的开销主要集中在两个方面 成本:
- 委派调用。如果你眯着眼看前面
Parallel.For
的例子,对Parallel.For
的调用看起来很像C# 循环或Visual Basic For循环。不要被愚弄:它仍然是一种方法 呼叫。这样做的一个结果是Parallel.For
的“身体” “loop”作为委托提供给方法调用。调用一个 委托产生与虚拟成本大致相同的成本 方法调用。- 线程之间的同步以实现负载平衡。尽管这些成本尽可能地降低,但任何负载量都会降低 平衡会产生一些成本,并且使用的负载平衡越多, 需要更多同步。
醇>对于中到大型环体,这些成本在很大程度上可以忽略不计。 但随着循环体的大小减小,开销变得越来越大 更引人注目。对于非常小的物体,环路可以完全 由此开销的成本占主导地位。支持非常的并行化 小循环体需要解决上面的#1和#2。
这种模式包括将输入分块到范围中,然后 而不是用并行循环替换顺序循环,包装 带有并行循环的顺序循环。
System.Concurrent.Collections.Partitioner
类提供了一个Create
方法重载,接受一个整数范围并返回一个OrderablePartitioner<Tuple<Int32,Int32>>
(Int64
的变体 而不是Int32
也可用):public static OrderablePartitioner<Tuple<long, long>> Create( long fromInclusive, long toExclusive);
超载
Parallel.ForEach
接受Partitioner<T>
的实例 和OrderablePartitioner<T>
作为来源,允许你通过 调用Partitioner.Create
来调用的结果Parallel.ForEach
。现在,想想Partitioner<T>
和。{OrderablePartitioner<T>
为IEnumerable<T>
。
Tuple<Int32,Int32>
表示从包含值到的范围 独家价值。考虑以下顺序循环:for (int i = from; i < to; i++) { // ... Process i. }
我们可以使用
Parallel.For
并行化,如下所示:Parallel.For(from, to, i => { // ... Process i. });
或者,我们可以使用
表示Parallel.ForEach
来调用Partitioner.Create
,在整个范围内包含一个连续循环 在Tuple<Int32, Int32>
中提供,inclusiveLowerBound
由元组的Item1
代表exclusiveUpperBound
由元组的Item2
:Parallel.ForEach(Partitioner.Create(from, to), range => { for (int i = range.Item1; i < range.Item2; i++) { // ... process i } });
虽然更复杂,但这使我们能够处理非常小的事情 循环体通过避开一些上述成本。而不是 为每个主体调用调用一个委托,我们现在正在分摊 委托调用的成本跨越分块中的所有元素 范围。另外,就并行循环而言,存在 只是要处理的几个元素:每个范围,而不是每个 指数。这隐含地降低了同步成本,因为 负载均衡的元素较少。
虽然
Parallel.For
应被视为最佳选择 并行化循环,如果性能测量显示 加速没有实现或者它们比预期的要小, 您可以尝试使用Parallel.ForEach
中显示的方法 与Partitioner.Create
合作。
另外,我考虑写Console
作为I / O的一种形式,所以我也建议阅读反模式部分&#34; PARALLEL LOOPS for I / O-BOUND WORKLOAD IN SCALABLE应用&#34;