C# 使用单个并行for循环获取Min、Max和

C# 使用单个并行for循环获取Min、Max和,c#,parallel-processing,task-parallel-library,C#,Parallel Processing,Task Parallel Library,我试图从一个大数组中获取最小值、最大值和总和(平均值)。我想用parallel.for替换我的常规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 (

我试图从一个大数组中获取最小值、最大值和总和(平均值)。我想用parallel.for替换我的常规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];
}
UInt16 tempMin=(UInt16)(Math.Pow(2,mfvm.cameras[openCamIndex].bitDepth)-1);
UInt16 tempMax=0;
UInt64 tempSum=0;
for(int i=0;itempMax)
tempMax=acquisition.frameDataShorts[i];
tempSum+=acquisition.frameDataShorts[i];
}
我知道如何使用自己切割阵列的任务来解决这个问题。然而,我很想学习如何使用parallel.for。因为据我所知,它应该能够做到非常优雅

我找到了计算和的方法,但是我不知道如何将它扩展到在一篇文章中完成所有三件事(最小值、最大值和和和)

结果: 好的,我尝试了PLINQ解决方案,我看到了一些严重的改进。 我的i7(2x4个内核)上有3个过程(最小、最大、和),比顺序aproach快4倍。然而,我在Xeon(2x8内核)上尝试了相同的代码,结果完全不同。并行(同样是3次)的速度实际上是顺序aproach的两倍(比我的i7快5倍)


最后,我自己用Task Factory分离了阵列,在所有计算机上的结果都稍好一些。

我不认为并行。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];
    }
}
编辑 只是为了好玩,由于对性能的评论,我做了一些测量。还有,我发现了这个

@10000000个值

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);
    }
}
公共类MinMaxSumTests{
[测试]
公共异步任务GetMinMaxSumAsync(){
var myArray=Enumerable.Range(010000).Select(x=>(long)x.ToArray();
var sw=新秒表();
sw.Start();
var maxTask=Task.Run(()=>myArray.Max());
var minTask=Task.Run(()=>myArray.Min());
var sumTask=Task.Run(()=>myArray.Sum());
var结果=等待任务.WhenAll(maxTask,
薄荷糖,
sumTask);
var max=结果[0];
var min=结果[1];
var总和=结果[2];
sw.Stop();
控制台写入线(软件延迟毫秒);
}
[测试]
public void GetMinMaxSum(){
var myArray=Enumerable.Range(010000).Select(x=>(long)x.ToArray();
var sw=新秒表();
sw.Start();
长tempMin=0;
长tempMax=0;
长tempSum=0;
for(int i=0;itempMax)
tempMax=myArray[i];
tempSum+=myArray[i];
}
sw.Stop();
控制台写入线(软件延迟毫秒);
}
}

我假设这里的主要问题是每次迭代都必须记住三个不同的变量。为此,您可以使用
元组

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);
        }
    }
);
var lockObject=新对象();
var arr=Enumerable.Range(0,1000000).ToArray();
长总计=0;
var min=arr[0];
var max=arr[0];
对于(0,棱长,
()=>新元组(0,arr[0],arr[0]),
(i,loop,temp)=>新元组(temp.Item1+arr[i],Math.Min(temp.Item2,arr[i]),
数学最大值(临时项目3,arr[i]),
x=>
{
锁定(锁定对象)
{
总计+=x.1项;
min=数学最小值(min,x.Item2);
max=数学最大值(max,x.Item3);
}
}
);

不过,我必须警告您,这个实现(在我的机器上)比您在问题中演示的简单for-loop方法慢10倍左右,因此请小心操作。

不要重新发明轮子,
Min
Max
Sum
,类似的操作是聚合。从.NET v3.5开始,您就有了方便的扩展方法版本,这些扩展方法已经为您提供了解决方案:

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
上的工作方式

在您的例子中,这是不够的,因为您显然不想多次迭代另一个数组,所以这里有一个魔术:(又称并行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.foreach
方法通过某种聚合方法重载,如下所示:

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;
如果您的数据需要额外的分区,您可以使用
分区器
进行以下方法:

var rangePartitioner = Partitioner.Create(0, sequence.Length);
        
Parallel.ForEach(
    // The input intervals
    rangePartitioner, 
    // same code here);
另外,
Aggregate
方法可用于
PLINQ
,具有一些合并逻辑
(请再次参阅MSDN中的插图):

有用链接:

  • 方法
  • 方法
  • 方法