开始学习多线程。有3种方法可以计算数组平方根的和,平均值和乘积。
首先,我使用PLINQ进行三次单独的阻止调用。然后我认为能够在一次调用中创建它并同时返回一个sum,product和average的对象会很好。我读到ParallelEnumerable.Aggregate可以帮助我,但我完全不知道如何使用它。
我真的很感激如何在我的案例中使用这个函数,这种方法的好/坏方面。
public static double Average(double[] array, string tool)
{
if (array == null) throw new ArgumentNullException(nameof(array));
double sum = Enumerable.Sum(array);
double result = sum / array.Length;
Print(tool, result);
return result;
}
public static double Sum(double[] array, string tool)
{
if (array == null) throw new ArgumentNullException(nameof(array));
double sum = Enumerable.Sum(array);
Print(tool, sum);
return sum;
}
public static void ProductOfSquareRoots(double[] array, string tool)
{
if (array == null) throw new ArgumentNullException(nameof(array));
double result = 1;
foreach (var number in array)
{
result = result * Math.Sqrt(number);
}
Print(tool, result);
}
答案 0 :(得分:3)
您想要计算的三个聚合值(平均值,平方和的乘积和乘积)可以通过对数字执行单次传递来计算。您可以执行此操作一次并在循环内聚合三个值(这可以节省时间),而不是这样做三次(每个聚合值一次)。
平均值是除以计数的总和,因为你已经在计算总和,除了获得平均值之外,你只需要计数。如果你知道输入的大小,你甚至不必计算项目,但在这里我假设输入的大小是事先未知的。
如果您想使用LINQ,可以使用Aggregate
:
var aggregate = numbers.Aggregate(
// Starting value for the accumulator.
(Count: 0, Sum: 0D, ProductOfSquareRoots: 1D),
// Update the accumulator with a specific number.
(accumulator, number) =>
{
accumulator.Count += 1;
accumulator.Sum += number;
accumulator.ProductOfSquareRoots *= Math.Sqrt(number);
return accumulator;
});
变量aggregate
是ValueTuple<int, double, double>
,其中包含Count
,Sum
和ProductOfSquareRoots
项。在C#7之前,您将使用匿名类型。但是,这将需要为输入序列中的每个值分配,从而减慢聚合。通过使用可变值元组,聚合应该变得更快。
Aggregate
适用于PLINQ,因此如果numbers
的类型为ParallelQuery<T>
而非IEnumerable<T>
,则聚合将并行执行。请注意,这要求聚合既是关联的(例如(a + b) + c = a + (b + c)
又是可交换的(例如a + b = b + a
),在您的情况下也是如此。
PLINQ有一个开销,因此与单线程LINQ相比,它可能表现不佳,具体取决于序列中元素的数量和计算的复杂程度。您必须自己测量,以确定PLINQ是否加快了速度。但是,您可以在LINQ和PLINQ中使用相同的Aggregate
表达式,通过将AsParallel()
插入正确的位置,可以轻松地将代码从单线程切换到并行。
答案 1 :(得分:2)
注意:您必须使用值1初始化result
变量,否则您将始终获得0。
注意2:代替Enumerable.Sum(array)
,只需撰写array.Sum()
。
不,请参阅Martin Liversage的回答。Aggregate
方法无法帮助您同时计算这三个函数。
KISS;)
if (array == null) throw new ArgumentNullException(nameof(array));
var sum = array.Sum();
var average = array.Average();
var product = array.Aggregate(1.0, (acc, val) => acc * Math.Sqrt(val));
可以简化:
var average = sum / array.Length;
这消除了额外的数组传递。
想要并行化吗?
var sum = array.AsParallel().Sum();
//var average = array.AsParallel().Average(); // Extra pass!
var average = sum / array.Length; // More fast! Really!
var product = array.AsParallel().Aggregate(1.0, (acc, val) => acc * Math.Sqrt(val));
但是,它可能比以前的方法慢。这种并行仅适用于数十亿元素的非常大的集合。
每次通过收集都需要时间。通过越少,性能越好。在计算平均值时,我们已经处理掉了一个。我们只做一个。
double sum = 0;
double product = 1;
foreach (var number in array)
{
sum += number;
product = product * Math.Sqrt(number);
}
double average = sum / array.Length;
一次通过三个结果!我们是最好的!
让我们回到主题。
Parallel.Invoke方法允许您并行执行多个函数,但它不会从中获取结果。它适用于“火灾和遗忘”类型的计算。
我们可以通过运行多个任务来并行化计算。在Task.WhenAll
的帮助下等待他们全部完成并获得结果。
var results = await Task.WhenAll(
Task.Run(() => array.Sum()),
Task.Run(() => array.Average()),
Task.Run(() => array.Aggregate(1.0, (acc, val) => acc * Math.Sqrt(val)))
);
var sum = results[0];
var average = results[1];
var product = results[2];
对于小尺寸的收藏也没有效果。但在某些情况下,它可能比AsParallel
更有效。
使用任务编写此方法的另一种方法。也许它会更清晰。
var sumTask = Task.Run(() => array.Sum());
var avgTask = Task.Run(() => array.Average());
var prodTask = Task.Run(() => array.Aggregate(1.0, (acc, val) => acc * Math.Sqrt(val)));
Task.WaitAll(sumTask, avgTask, prodTask);
sum = sumTask.Result;
average = avgTask.Result;
product = prodTask.Result;