我试图从大型数组获得最小值,最大值和总和(平均值)。我很乐意用parallel.for替换我的常规循环。
UInt16 tempMin = (UInt16)(Math.Pow(2,mfvm.cameras[openCamIndex].bitDepth) - 1);
UInt16 tempMax = 0;
UInt64 tempSum = 0;
for (int i = 0; i < acquisition.frameDataShorts.Length; i++)
{
if (acquisition.frameDataShorts[i] < tempMin)
tempMin = acquisition.frameDataShorts[i];
if (acquisition.frameDataShorts[i] > tempMax)
tempMax = acquisition.frameDataShorts[i];
tempSum += acquisition.frameDataShorts[i];
}
我知道如何使用任务自行切割数组来解决这个问题。但是,我很想学习如何使用parallel.for。因为据我所知,它应该能够非常优雅地做到这一点。
我发现this tutorial from MSDN用于计算Sum,但是我不知道如何扩展它以在一个段落中完成所有三件事(min,max和sum)。
结果: 好的,我尝试过PLINQ解决方案,我看到了一些重大改进。 3次传球(Min,Max,Sum)在我的i7(2x4核心)上比连续的aproach快4倍。但是我在Xeon(2x8核心)上尝试了相同的代码,结果完全不同。并行(再次3遍)实际上是顺序aproach的两倍(这比我的i7快5倍)。
最后,我自己将数组与Task Factory分开,我在所有计算机上的结果都略胜一筹。
答案 0 :(得分:1)
我认为parallel.for不适合这里,但试试这个:
public class MyArrayHandler {
public async Task GetMinMaxSum() {
var myArray = Enumerable.Range(0, 1000);
var maxTask = Task.Run(() => myArray.Max());
var minTask = Task.Run(() => myArray.Min());
var sumTask = Task.Run(() => myArray.Sum());
var results = await Task.WhenAll(maxTask,
minTask,
sumTask);
var max = results[0];
var min = results[1];
var sum = results[2];
}
}
修改强> 由于有关性能的评论,我只是为了好玩,我做了几次测量。另外,找到了Fastest way to find sum。
@ 10,000,000值
GetMinMax:218ms
GetMinMaxAsync:308ms
public class MinMaxSumTests {
[Test]
public async Task GetMinMaxSumAsync() {
var myArray = Enumerable.Range(0, 10000000).Select(x => (long)x).ToArray();
var sw = new Stopwatch();
sw.Start();
var maxTask = Task.Run(() => myArray.Max());
var minTask = Task.Run(() => myArray.Min());
var sumTask = Task.Run(() => myArray.Sum());
var results = await Task.WhenAll(maxTask,
minTask,
sumTask);
var max = results[0];
var min = results[1];
var sum = results[2];
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
[Test]
public void GetMinMaxSum() {
var myArray = Enumerable.Range(0, 10000000).Select(x => (long)x).ToArray();
var sw = new Stopwatch();
sw.Start();
long tempMin = 0;
long tempMax = 0;
long tempSum = 0;
for (int i = 0; i < myArray.Length; i++) {
if (myArray[i] < tempMin)
tempMin = myArray[i];
if (myArray[i] > tempMax)
tempMax = myArray[i];
tempSum += myArray[i];
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
}
}
答案 1 :(得分:1)
我认为这里的主要问题是每次迭代都要记住三个不同的变量。您可以将Tuple
用于此目的:
var lockObject = new object();
var arr = Enumerable.Range(0, 1000000).ToArray();
long total = 0;
var min = arr[0];
var max = arr[0];
Parallel.For(0, arr.Length,
() => new Tuple<long, int, int>(0, arr[0], arr[0]),
(i, loop, temp) => new Tuple<long, int, int>(temp.Item1 + arr[i], Math.Min(temp.Item2, arr[i]),
Math.Max(temp.Item3, arr[i])),
x =>
{
lock (lockObject)
{
total += x.Item1;
min = Math.Min(min, x.Item2);
max = Math.Max(max, x.Item3);
}
}
);
但是,我必须警告你,这个实现比你在问题中演示的简单for循环方法慢了大约10倍(在我的机器上),所以请谨慎行事。
答案 2 :(得分:1)
请勿重新发明轮子,Min
,Max
Sum
,类似的操作是聚合。从.NET v3.5开始,您就拥有了LINQ
扩展方法的便捷版本,这些方法已经为您提供了解决方案:
using System.Linq;
var sequence = Enumerable.Range(0, 10).Select(s => (uint)s).ToList();
Console.WriteLine(sequence.Sum(s => (double)s));
Console.WriteLine(sequence.Max());
Console.WriteLine(sequence.Min());
虽然它们被声明为IEnumerable
的扩展名,但它们对IList
和Array
类型有一些内部改进,因此您应衡量代码的方式将在IEnumerable
上对这些类型进行处理。
在你的情况下,这还不够,因为你显然不想多次迭代其他一个数组,所以魔术就在这里:PLINQ
(a.k.a。Parallel-LINQ
)。您只需要添加一个方法来并行聚合数组:
var sequence = Enumerable.Range(0, 10000000).Select(s => (uint)s).AsParallel();
Console.WriteLine(sequence.Sum(s => (double)s));
Console.WriteLine(sequence.Max());
Console.WriteLine(sequence.Min());
此选项为项目的同步添加了一些开销,但它可以很好地扩展,为小型和大型枚举提供类似的时间。来自MSDN:
每当您需要将并行聚合模式应用于.NET应用程序时,
PLINQ
通常是推荐的方法。它的声明性使其比其他方法更不容易出错,并且它在多核计算机上的性能与它们相比具有竞争力。使用
PLINQ
实现并行聚合并不需要在代码中添加锁。相反,所有同步都在PLINQ
内部发生。
但是,如果您仍想调查不同类型操作的性能,可以使用Parallel.For
和Parallel.ForaEach
方法重载一些聚合方法,如下所示:
double[] sequence = ...
object lockObject = new object();
double sum = 0.0d;
Parallel.ForEach(
// The values to be aggregated
sequence,
// The local initial partial result
() => 0.0d,
// The loop body
(x, loopState, partialResult) =>
{
return Normalize(x) + partialResult;
},
// The final step of each local context
(localPartialSum) =>
{
// Enforce serial access to single, shared result
lock (lockObject)
{
sum += localPartialSum;
}
}
);
return sum;
如果您需要为数据添加其他分区,可以使用Partitioner
方法:
var rangePartitioner = Partitioner.Create(0, sequence.Length);
Parallel.ForEach(
// The input intervals
rangePartitioner,
// same code here);
同样Aggregate
方法可用于PLINQ
,具有一些合并逻辑
(再次来自MSDN的插图):
有用的链接: