我有一个方法可以计算每次迭代中的一些数组,然后对它们求和(按索引方式添加每个数组)并返回总和数组。
我正在寻找一种高效的并行方法。本质上,我想避免不断创建数组并重新使用它们。
示例:
private void Computation(float[] result)
{
var _lock = new object();
Parallel.For(0, 50, x =>
{
var subResult = new float[200*1000];
CalculateSubResult(subResult);
lock(_lock)
for (int i = 0; i < subResult.Length; i++)
result[i] += subResult[i];
});
}
问题是这些Parallel.For's
是嵌套的(因为CalculateSubResult
也做类似的循环)。这导致不断创建和丢弃用于保存子计算结果的数组。
要解决此问题,我使用ThreadLocal
来存储缓存的数组:
ThreadLocal<float[]> threadLocalArray = new ThreadLocal<float[]>(()=>new float[200*1000]);
private void ComputationWithThreadLocal(float[] result)
{
var _lock = new object();
Parallel.For(0, 50, x =>
{
var subResult = threadLocalArray.Value;
CalculateSubResult(subResult);
lock (_lock)
for (int i = 0; i < subResult.Length; i++)
result[i] += subResult[i];
Array.Clear(subResult,0,subResult.Length);
});
}
这种方法存在两个问题:
随着时间的推移(这是长时间运行计算的一部分)threadLocalArray
存储越来越多的数组,即使只有很少的逻辑核心,也会导致内存泄漏。在计算完成之前我无法调用threadLocalArray.Dispose()
,因此无法阻止它的增长。
总结子计算结果需要锁定并且浪费。我希望有一个本地的每线程总和,并且只有在线程完成其分区工作后,才会发生求和。
有一种处理2的重载方法。
public static ParallelLoopResult For<TLocal>(
int fromInclusive,
int toExclusive,
Func<TLocal> localInit,
Func<int, ParallelLoopState, TLocal, TLocal> body,
Action<TLocal> localFinally
)
Func<TLocal> localInit
只会初始化一个subResult数组:()=>new float[200*1000]
。在这种情况下,Action<TLocal> localFinally
每个分区只执行一个,这样更有效。但问题是localInit
不能使用()=>threadLocalArray.Value
来获取线程局部数组,因为如果Parallel.For循环是递归嵌套的,那么同一个数组用于不同嵌套级别的子计算,导致不正确的结果
我想我正在寻找类似的东西(CachedArrayProvider是一个需要管理重用数组的类):
Parallel.For(0,50,
()=>CachedArrayProvider.GetArray(),
(i, localArray) => {/*body*/},
(localArray) =>
{
//do final sum here
CachedArrayProvider.ReturnArray(localArray);
}
);
有什么想法吗?