C++ 并行化嵌套for循环:打包数据

C++ 并行化嵌套for循环:打包数据,c++,c,openmp,nested-loops,C++,C,Openmp,Nested Loops,我有两个带整数参数的函数;叫他们f和g。我还有另一个函数h,它有两个整数参数。给定一个大小为D的平方U(意思是:{m0,m0+1,…,m0+D-1}x{n0,n0+1,…,n0+D-1}),我有一个程序来计算f(n)g(m)h(n,m)在时间上的和,在D上大致线性,给定数组farr,garr包含f(m0),f(m0+1),…,f(m0+D-1)和g(n0),g(n0+1),…);让我们将该过程视为一个黑箱,我们通过调用和(farr,garr,m0,n0,D)调用它。我们可以通过调用Fillf(f

我有两个带整数参数的函数;叫他们f和g。我还有另一个函数h,它有两个整数参数。给定一个大小为D的平方U(意思是:{m0,m0+1,…,m0+D-1}x{n0,n0+1,…,n0+D-1}),我有一个程序来计算f(n)g(m)h(n,m)在时间上的和,在D上大致线性,给定数组
farr
garr
包含f(m0),f(m0+1),…,f(m0+D-1)和g(n0),g(n0+1),…);让我们将该过程视为一个黑箱,我们通过调用和(farr,garr,m0,n0,D)调用它。我们可以通过调用Fillf(f,m0,D)和Fillg(g,m0,D),在时间上大致线性地计算farr[0]=f(m0),…farr[D-1]=f(m0+D-1)或garr[0]=garr(n0),garr[1]=g(n0+1),…,garr[n0+D-1]

问题是如何有效地并行计算{0,1,…,rD-1}x{0,1,…,rD-1}(假设)中所有(n,m)的f(n)g(m)h(n,m)之和。这在抽象上很简单——我想知道的是如何在OpenMP中实现这一点

最简单的方法可能是这样的:

S=0;
#pragma omp parallel for collapse(2) schedule(dynamic) private(m0,n0,farr,garr) reduction(+:S)
for(m0=0; m0<r*D; m0+=D)
 for(n0=0; n0<r*D; n0+=D)
  farr = (short *) calloc(D,sizeof(int));
  Fillf(farr,m0,D);
  garr = (short *) calloc(D,sizeof(int));
  Fillg(garr,n0,D);
  S+=Sum(farr,garr,m0,n0,D)
  free(garr);
  free(farr);
这也是一个有效的解决方案,但是:(a)每个
garr
段仍然得到r次而不是一次计算,(b)如果可用线程的数量远远大于
r
(但小于
r^2
),并行化将是低效的。这里我们不能使用
collapse(2)
,因为在这两个循环之间发生了一些事情


显然应该可以做得更好。使用OpenMP编写或多或少显而易见的过程的简单方法是什么?(一个人是否应该预先计算大小约为sqrt(s)D的
farr
garr
段,其中s是可用线程的数量,然后对长度约为D sqrt(s)的段执行嵌套循环,并对
m0
n0
进行折叠(2))

如果要避免重复计算
farr
garr
,则至少有两个选项:

  • 在单独的循环中提前计算并存储所有的
    garr
    ,并采用与第二种方法相同的方法。也可以选择提前计算并存储所有的
    farr

  • 修改第二个备选方案以并行化内部循环而不是外部循环。这还将使您能够将
    farr
    的分配和解除分配从循环中提出来:

    S = 0;
    farr = malloc(D, sizeof(int));
    for (int m0 = 0; m0 < r * D; m0 += D) {
        int S2 = 0;
        Fillf(farr, m0, D);
        #pragma omp parallel for private(farr, m0) reduction(+:S2)
        for (int n0 = 0; n0 < r * D; n0 += D) {
            int *garr = calloc(D, sizeof(int));
            Fillg(garr, n0, D);
            S2 += Sum(farr, garr, m0, n0, D)
            free(garr);
        }
        S += S2;
    }
    free(farr);
    
    S=0;
    farr=malloc(D,sizeof(int));
    对于(int m0=0;m0
  • 还请注意

    • 如果
      Fillf()
      假设它的数组参数最初是零填充的(正如
      calloc()
      所确保的那样),那么将内存分配从循环中提升出来需要在每次调用之前手动进行零填充,但这仍然可能比释放和重新分配便宜
    • 我删除了scheduling子句,因为动态调度似乎对这种计算没有任何好处
    • 声明
      farr
      private只会使指针私有,而不是它所指向的数据。此外,由于在并行循环中既不修改
      farr
      也不修改
      m0
      ,因此将它们声明为私有实际上没有什么用处。我在示例代码中留下了
      private
      子句,主要是作为这些评论的焦点
    • 重新计算
      garr
      的各种值可以节省细粒度并行带来的额外开销。如果内部循环的每个迭代的工作量仍然相对较大,那么这更有可能是一个胜利

    谢谢。1.这并不是一个真正的选择,因为空间的复杂性可能会急剧上升。在大小为sqrt(s)D的块中计算f和g是否更有意义,其中s是线程数?还有,为什么第二种选择比第一种更可取呢?我实际上试过你的选择2。第一。问题是(在我看来)r可能比线程的数量小很多。(假设r^2至少与线程数一样大,这样我们就有希望高效地使用资源。)事实上,在上面列出的选项中,对于我的特定应用程序运行最快的是第一个(“最简单的方法”),即使它多次计算f和g的每个值。我采用声明int n0,最内层循环中的int*garr与私有循环具有相同的效果?最后,如果在某些方块中计算f(n)g(m)h(n,m)之和比在其他方块中花费更多的时间,动态调度是否有帮助?当然,任何试图通过将sqrt(s)D分解成多个部分来提高过程效率的尝试都会受到损害,因此我们不妨假设情况并非如此。@HAHelfgott,是的,预计算所有数据可能会对存储提出比您希望的更高的要求。这是时间与空间的权衡。但是,如果您并行化外部循环(有或没有
    collapse(2)
    ),并使用多个线程来执行它,那么我认为没有其他方法可以避免重复计算
    garr
    。毕竟,这就是问题所在。然而,这种复制产生更快的代码并非不可能。
    S = 0;
    farr = malloc(D, sizeof(int));
    for (int m0 = 0; m0 < r * D; m0 += D) {
        int S2 = 0;
        Fillf(farr, m0, D);
        #pragma omp parallel for private(farr, m0) reduction(+:S2)
        for (int n0 = 0; n0 < r * D; n0 += D) {
            int *garr = calloc(D, sizeof(int));
            Fillg(garr, n0, D);
            S2 += Sum(farr, garr, m0, n0, D)
            free(garr);
        }
        S += S2;
    }
    free(farr);