7

我正在使用 OpenMP 编写一个矩阵乘法程序,为了缓存方便,实现乘法 A x B(转置)行 X 行而不是经典的 A x B 行 x 列,以提高缓存效率。这样做我遇到了一个有趣的事实,对我来说是不合逻辑的:如果在这段代码中我并行化外部循环,则程序比我将 OpenMP 指令放在最内部的循环中要慢,在我的计算机中,时间是 10.9 秒对 8.1 秒。

//A and B are double* allocated with malloc, Nu is the lenght of the matrixes 
//which are square

//#pragma omp parallel for
for (i=0; i<Nu; i++){
  for (j=0; j<Nu; j++){
    *(C+(i*Nu+j)) = 0.;
#pragma omp parallel for
    for(k=0;k<Nu ;k++){
      *(C+(i*Nu+j))+=*(A+(i*Nu+k)) * *(B+(j*Nu+k));//C(i,j)=sum(over k) A(i,k)*B(k,j)
    }
  }
}
4

2 回答 2

4

当您并行化外部循环并且编译器无法弄清楚并添加额外的锁时,您可能在数据中有一些依赖关系。

很可能它决定不同的外部循环迭代可以写入相同的(C+(i*Nu+j))内容,并添加访问锁来保护它。

如果您要并行化第二个循环,编译器可能会发现没有依赖关系。但是,对于编译器来说,找出并行外部循环没有依赖项并不是那么简单。

更新

一些性能测量。

你好,我们又见面了。它看起来像 1000 双*+不足以支付线程同步的成本。

我做了一些小测试,简单的向量标量乘法对 openmp 无效,除非元素的数量少于 ~10'000。基本上,您的阵列越大,使用 openmp 将获得更多的性能。

因此,并行化最内部的循环,您必须在不同线程之间分离任务并收集数据 1'000'000 次。

PS。试试 Intel ICC,它可以免费用于学生和开源项目。我记得使用 openmp 来处理小于 10'000 个元素的数组。

更新 2:减少示例

    double sum = 0.0;
    int k=0;
    double *al = A+i*Nu;
    double *bl = A+j*Nu;
    #pragma omp parallel for shared(al, bl) reduction(+:sum)
    for(k=0;k<Nu ;k++){
        sum +=al[k] * bl[k]; //C(i,j)=sum(over k) A(i,k)*B(k,j)
    }
    C[i*Nu+j] = sum;
于 2011-01-18T17:00:07.667 回答
4

尝试减少击中结果的频率。这会导致缓存线共享并防止操作并行运行。相反,使用局部变量将允许大多数写入发生在每个内核的 L1 缓存中。

此外,使用restrict可能会有所帮助。否则编译器不能保证写入C不会改变AB.

尝试:

for (i=0; i<Nu; i++){
  const double* const Arow = A + i*Nu;
  double* const Crow = C + i*Nu;
#pragma omp parallel for
  for (j=0; j<Nu; j++){
    const double* const Bcol = B + j*Nu;
    double sum = 0.0;
    for(k=0;k<Nu ;k++){
      sum += Arow[k] * Bcol[k]; //C(i,j)=sum(over k) A(i,k)*B(k,j)
    }
    Crow[j] = sum;
  }
}

另外,如果您并行化最内层循环,我认为 Elalfer 需要减少是正确的。

于 2011-01-18T18:57:20.373 回答