OpenMP并行代码运行变慢了

3
我有两个循环需要并行化。
#pragma omp parallel for
  for (i = 0; i < ni; i++)
    for (j = 0; j < nj; j++) {
      C[i][j] = 0;
      for (k = 0; k < nk; ++k)
        C[i][j] += A[i][k] * B[k][j];
    }
#pragma omp parallel for
  for (i = 0; i < ni; i++)
    for (j = 0; j < nl; j++) {
      E[i][j] = 0;
      for (k = 0; k < nj; ++k)
        E[i][j] += C[i][k] * D[k][j];
    }

奇怪的是,即使使用大量线程,顺序执行的速度也比上面的并行版本要快得多。我做错了什么吗?请注意,所有数组都是全局的。这会产生差异吗?


1
自C99以来,C语言拥有了for循环局部变量。只需帮助编译器并使用它们即可。 - Jens Gustedt
1
#pragma omp parallel for private(j,k) - Z boson
1个回答

6
你的并行外循环迭代共享内部循环的索引变量(jk)。这肯定会使你的代码比你预期的要慢一些,也就是说,你的循环不是"尴尬"(或者“令人愉快”)的并行化,而并行循环迭代需要从共享内存中访问这些变量。
更糟糕的是,由于这个原因,你的代码包含竞争条件。结果它将表现为不确定性。换句话说:你的并行矩阵乘法实现现在是错误的!(继续检查你的计算结果吧。;))
你想做的是确保所有外部循环的迭代都有自己的私有索引变量jk的副本。你可以通过在并行循环的范围内声明这些变量来实现这一点:
int i;

#pragma omp parallel for
  for (i = 0; i < ni; i++) {
    int j1, k1;  /* explicit local copies */
    for (j1 = 0; j1 < nj; j1++) {
      C[i][j1] = 0;
      for (k1 = 0; k1 < nk; ++k1)
        C[i][j1] += A[i][k1] * B[k1][j1];
    }
  }        
#pragma omp parallel for
  for (i = 0; i < ni; i++) {
    int j2, k2;  /* explicit local copies */
    for (j2 = 0; j2 < nl; j2++) {
      E[i][j2] = 0;
      for (k2 = 0; k2 < nj; ++k2)
        E[i][j2] += C[i][k2] * D[k2][j2];
    }
  }

或者在您的循环指示中将它们声明为private

int i, j, k;

#pragma omp parallel for private(j, k)
  for (i = 0; i < ni; i++)
    for (j = 0; j < nj; j++) {
      C[i][j] = 0;
      for (k = 0; k < nk; ++k)
        C[i][j] += A[i][k] * B[k][j];
    }
#pragma omp parallel for private(j, k)
  for (i = 0; i < ni; i++)
    for (j = 0; j < nl; j++) {
      E[i][j] = 0;
      for (k = 0; k < nj; ++k)
        E[i][j] += C[i][k] * D[k][j];
    }

这些更改能使并行实现比顺序实现更快吗?很难说。这取决于问题的规模。并行化(特别是通过OpenMP进行并行化)会带来一些开销。只有当您生成足够的并行工作时,将工作分配到并行线程上所获得的收益将超过产生的开销成本。
为了找出您的代码和软件/硬件平台需要多少工作量,我建议通过使用不同的矩阵大小运行代码来进行实验。然后,如果您还预计将“太小”的矩阵大小作为计算的输入,您可能希望将并行处理设置为条件性处理(例如,通过在循环pragma上装饰if子句来实现)。
#pragma omp parallel for private (j, k) if(ni * nj * nk > THRESHOLD)
  for (i = 0; i < ni; i++) {
     ...
  }
#pragma omp parallel for private (j, k) if(ni * nl * nj > THRESHOLD)
  for (i = 0; i < ni; i++) {
    ...
  }

1
或者只需使用“for”本地变量。 C语言有它,OpenMp也有它。养成良好习惯后,事情会更加顺利 :) - Jens Gustedt
@JensGustedt 我会使用它,但我不知道我可以得到什么样的输入!我正在开发自动并行化功能,识别那些没有依赖关系的循环,并为其插入OpenMP指示语。 - coder hacker
@TejasPatel 反依赖是由内部循环变量引起的。你考虑过使用Pareon作为参考吗(厚颜无耻地推销!)? - Stefan Holdermans
@JensGustedt 你并不总是有选择的余地。特别是在编写某种代码处理器时。 - Stefan Holdermans
@StefanHoldermans,为什么你不把i放在私有变量列表中呢?我猜想你是希望在for-loop上面使用for-directive来处理i作为私有变量的情况,我同意这一点。但是当我在自己的示例中将i作为私有变量使用时,我意外地得到了更快的实现。那么这是怎么回事呢? - gonidelis
显示剩余14条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接