linq C#作为并行与并行:如何减少合并结果的频率?

np8igboo  于 2022-12-06  发布在  C#
关注(0)|答案(1)|浏览(165)

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进行线程局部归约。
  • 在并行循环之后,合并线程局部导致串行或另一个并行缩减(另一个并行循环)。
20jt8wwn

20jt8wwn1#

您可以先将循环组合为3元组(i,j,k),然后在其上运行AsParallel()。要限制并行处理操作的并发任务数,请使用WithDegreeOfParallelism(x)。这样,您的数据将被分区,每个分区将被并行处理。要聚合此分区的结果,请使用Aggregate函数,但要注意使用为ParallelQuery提供的重载,而不是常规的IEnumerable

var result = Enumerable.Range(1, 1000).AsParallel()
    .WithDegreeOfParallelism(4)
    .Aggregate(
    0, // initial seed
    (acc, value) => acc + value, // this is executed for accumulators in each partition, separately, updating partition accumulator
    (acc1, acc2) => acc1 + acc2, // this is to combine partition accumulators to get final one
    (acc) => acc); // this is to convert the final accumulator into final result (if necessary), so what will be returned

相关问题