C#作为并行/并行:如何减少合并结果的频率?
举个例子,一个嵌套的循环迭代很多次,作为一个并行约简。(或者叫它map reduce)
// Dictionary size is small and easily contend.
Dictionary<SumDataCategory, SumData> globalResult;
// A loop to be parallelized.
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
for (int k = 0; k < N; k++) {
// Pull inputs depending on i,j,k.
InputData[] inputs = fetchInputData(i, j, k);
// Do something independent calculation.
SumData sum1 = SumData.Sum(inputs);
// Finally reduction operation. <- to optimize
globalResult[sum1.Category].Sum(sum1);
}
}
}
目标是获得唾手可得的成果-以较小的努力提高缩减性能。(不重写整个循环/处理主体)
工作负载特性的限制:
- 它不是完全规则的:不应假定为静态分区。
- 它不是高度动态的:不需要动态作业/工作负载产生。
分析:
- 每次迭代减少到全局状态是一种浪费,并且高速缓存争用很高。
迭代/输入数量比CPU内核数量多。
- 手动分区和处理分区是非常重要的。(与普通的AsParallel/Parallel或OpenMP相比)
因为C# AsParallel/Parallel是没有编译器帮助的库(不像OpenMP或数据并行解决方案),而且大多数肮脏的工作必须由用户完成。
- 即使应用了分区,按块(分区数据)缩减到全局状态仍然是浪费,并且更复杂。(与最终缩减相比,因为最终缩减可以是串行或并行的,而按块缩减是并发的)
我想不出一个好办法:
- 如果我使用thread-local,我就找不到时间拉取thread-local并合并它们。
- 如果我手动记录每个线程的结果,我需要一个可靠的线程ID,并提前知道ID范围。(OpenMP和数据并行解决方案都有)
作为参考,在OpenMP中,我将这样做:
- 在并行循环之前,设置或查询线程计数。
- 分配每个线程的缩减数据。
- 并行循环,并按线程id进行线程局部归约。
- 在并行循环之后,合并线程局部导致串行或另一个并行缩减(另一个并行循环)。
1条答案
按热度按时间20jt8wwn1#
您可以先将循环组合为3元组
(i,j,k)
,然后在其上运行AsParallel()
。要限制并行处理操作的并发任务数,请使用WithDegreeOfParallelism(x)
。这样,您的数据将被分区,每个分区将被并行处理。要聚合此分区的结果,请使用Aggregate
函数,但要注意使用为ParallelQuery提供的重载,而不是常规的IEnumerable
。