考虑这段代码,它扩展了Array .NET类型:
Public Module ArrayExtensions
<System.Runtime.CompilerServices.Extension>
Public Iterator Function ToEnumerable(Of T)(target As Array) As IEnumerable(Of T)
For Each item In target
Yield DirectCast(item, T)
Next
End Function
End Module
我用它来获得Min()和Max()扩展方法将采用的结构。阵列通常在三维中运行到数百万个元素,例如数组T(,,)
很常见。
编辑:具体来说,这个函数与一行代码一起发挥作用:
Return loadedData(rType).dataArray.ToEnumerable(Of Single).Min
其中dataarray
(在这种情况下)是ConcurrentDictionary
loadedData
中的值项,其类型为Single(,,)
如果没有当前编写的ToEnumerable
,则没有IEnumerable接口可以挂接Max()
扩展函数。
“并行化”此功能需要什么?我尝试过的Parallel.For
形式似乎无效,因为loadedData
数组未被识别为IEnumerable
类型。 (这是因为单个(,,)被处理为值类型,也许?)
(没有答案必须使用VB.C#也没关系!)
答案 0 :(得分:1)
由于您已经拥有IEnumerable<T>
,因此您可以使用AsParallel()
(例如dataArray.ToEnumerable().AsParallel().Min()
)。但IEnumerable
接口本质上是串行的,您可以并行化处理其元素,但不能迭代它。这意味着对于像Min()
这样非常简单的操作,这种并行化没有多大意义。
这里有意义的是将迭代并行化。这是可能的,因为您可以使用索引器访问数组的特定项目。
我尝试使用custom partitioner执行此操作,但结果比串行版本更差。问题是每次迭代的开销必须尽可能小,并且很难直接使用分区器。
相反,您可以做的是仅对数组的第一个维度进行分区(假设您可以确定它至少与您的CPU数量一样大),然后使用仅返回ToEnumerable()
的版本第一个维度的一部分。类似的东西:
private static IEnumerable<T> ToEnumerable<T>(this T[,,] array, int from, int to)
{
for (int i = from; i < to; i++)
{
for (int j = 0; j < array.GetLength(1); j++)
{
for (int k = 0; k < array.GetLength(2); k++)
{
yield return array[i, j, k];
}
}
}
}
Partitioner.Create(0, data.GetLength(0))
.AsParallel()
.Select(range => data.ToEnumerable(range.Item1, range.Item2).Min())
.Min()
这大约是我计算机上串行版本的两倍。但是这仍然有枚举器的开销,这在这种情况下非常重要:这个版本大约是上面的并行代码的两倍:
var length0 = data.GetLength(0);
var length1 = data.GetLength(1);
var length2 = data.GetLength(2);
float min = float.MaxValue;
for (int i = 0; i < length0; i++)
{
for (int j = 0; j < length1; j++)
{
for (int k = 0; k < length2; k++)
{
float value = data[i, j, k];
if (value < min)
min = value;
}
}
}
return min;
现在我们可以并行化这段代码,这会导致大约四倍的加速(如前所述,我们对第一个维度进行分区,然后按顺序继续):
var results = new ConcurrentQueue<float>();
var length1 = data.GetLength(1);
var length2 = data.GetLength(2);
Parallel.ForEach(
Partitioner.Create(0, data.GetLength(0)), range =>
{
float min = float.MaxValue;
for (int i = range.Item1; i < range.Item2; i++)
{
for (int j = 0; j < length1; j++)
{
for (int k = 0; k < length2; k++)
{
float value = data[i, j, k];
if (value < min)
min = value;
}
}
}
results.Enqueue(min);
});
return results.Min();
但是等等!还有更多。多维数组在.Net中非常慢,因此从性能角度来看,使用锯齿状数组(float[][][]
而不是float[,,]
)是有意义的,即使多维数组更适合。使用它,我们可以获得大约50%的加速:
dataJagged.AsParallel().Min(
level1 =>
{
float min = float.MaxValue;
foreach (var level2 in level1)
{
for (int k = 0; k < level2.Length; k++)
{
float value = level2[k];
if (value < min)
min = value;
}
}
return min;
});
总而言之,我的计算机上有一个使用各种方法的时间表:
ToEnumerable
,序列号:18.6 s ToEnumerable
,PLINQ:7.4 s Parallel.ForEach
:1.1 s