【问题标题】:OpenMP is not reducing run time even though multiple threads are running. How can this be即使多个线程正在运行,OpenMP 也不会减少运行时间。怎么会这样
【发布时间】:2023-04-04 23:53:01
【问题描述】:

我正在尝试对更大的矩阵(1000x1000 到 5000x5000 双精度)进行乘法运算。我必须使用 OpenMP 来并行化乘法。并行 for 循环由 p 个线程处理,我猜它们是根据打印出 omp_get_thread_num() 正确调度的。 我在 4 核 CPU 上运行,并确认最大线程数为 4。如果这有什么不同,CPU 是虚拟的。 问题是当我更改线程的 nb 时运行时间并没有减少。

lscpu results

  • 我检查了libgomp库是由ldconfig -p | grep -i "gomp"安装的。

  • 我已尝试将并行循环的位置更改为嵌套循环之一。

  • 我已尝试更改调度和块大小。

#include <stdio.h>
#include <stdlib.h>
#include <omp.h>
#include <time.h>

double** createMatrix(int N)
{
  double** rndMatrix;
  srand48((long int)time(NULL));
  rndMatrix = malloc(sizeof(double*)*N);
  int n,m;

  for(n=0; n<N; n++){
      rndMatrix[n] = malloc(sizeof(double*)*N);
      for (m=0;m<N;m++){
          rndMatrix[n][m] = drand48();
      }
  }
  return rndMatrix;
}

void problem1(double** a, double** b, int N, int p){
    int i,k,j;
  int g;
  double** c;
  c = malloc(sizeof(double*)*N);

  for(g=0; g<N; ++g)
      c[g] = malloc(sizeof(double*)*N);

  //Timer start
  clock_t tStart = clock();
  //time_t tStart, tEnd;
  //tStart =time(NULL);

  //Parallelised part
#pragma omp parallel shared(a,b,c,N) private(i,k,j) num_threads(p)
  {
#pragma omp for schedule(static) nowait
      for(i=0; i<N; ++i){
          for(j=0; j<N; ++j){
                  double sum = 0;
                  for(k=0; k<N; ++k){
                      sum += a[i][k] * b[k][j];
                  }
                  c[i][j]=sum;
          }
      }
  }

  //Timer end
  printf("Time taken: %.2fs\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);
  //tEnd = time(NULL);
  //printf("Time taken: %ds\n",  tEnd - tStart);
}


int main(void)
{
  int p=0;
  int N=0;
  //User input:

  printf("Enter matrix dimension:\n");
  scanf("%d", &N);

  printf("Please enter nb of threads:\n");
  scanf("%d", &p);

  double **a;
  double **b;

  a = createMatrix(N);
  sleep(2);
  b = createMatrix(N);

  problem1(a,b,N,p);

  return 0;
}

【问题讨论】:

  • 1) 你如何测量时间 2) 你运行它的确切处理器是什么? 3)你如何编译代码? 4) 请提供minimal reproducible example
  • 1) 如代码所示,我同时使用了clock() 和time()。 2)lscpu的结果可以在添加的图片中看到。 3) gcc -o3 -fopenmp openmpMain.c o2 或 o3 需要分配 4) 我将上面的代码编辑为可运行
  • 您使用clocktime 得到相同的结果吗?
  • 结果是一样的。除了那个clock()返回了更多的小数。

标签: c parallel-processing openmp


【解决方案1】:

您使用了不正确的算法以 ijk 顺序乘以您的矩阵。

for(i=0; i<N; ++i){
      for(j=0; j<N; ++j){
           double sum = 0;
           for(k=0; k<N; ++k){
                sum += a[i][k] * b[k][j];
           }
           c[i][j]=sum;
       }
}

每当 k 在内部循环中递增时,b 会逐列遍历并生成缓存未命中。结果是每次迭代都有一个缓存未命中。这将在很大程度上支配计算时间,并且您的算法受内存限制。

您可以增加内核数,但不会增加内存带宽(缓存大小的轻微增加可能会略微缩短计算时间)。

Open-MP 仅适用于核心受限问题,不适用于内存受限计算。

要查看额外内核的效果,您必须使用另一种算法。例如,通过将迭代顺序更改为 ikj。

    for(i=0; i<N; ++i){
      for(k=0; k<N; ++k){
        double r = a[i][k];
        for(j=0; j<N; ++j){
          c[i][j] += r * b[k][j];
        }
      }
    }

当内部索引 (j) 递增时,c[i][j] 和 b[i][j] 将逐行遍历。不是每次迭代一次未命中,而是每八次迭代只有两次未命中,并且内存带宽将不再是限制因素。您的计算时间将大大减少,并且会随着使用的内核数量而扩展。

耗时(N=2000,p=1):4.62s
所用时间(N=2000,p=2):3.03s
耗时(N=2000,p=4):2.34s

ikj 不是唯一的方法。您还可以使用分块矩阵乘法,其中乘法由 ijk 完成,但在适合 LI 缓存的小矩阵上。

#define BL 40
  for (int jj=0;jj<N;jj+=BL)
    for (int kk=0;kk<N;kk+=BL)
      for (i=0;i<N;i++)
        {
          for (j=jj;j<min(jj+BL-1,N);j++)
        {
          double sum=0.0;
          for (k=kk;k<min(kk+BL-1,N);k++)
            sum += a[i][k]*b[k][j];
          c[i][j]=sum;
        }
        }

  }

算法稍长,但由于避免了缓存未命中,因此也受到内核限制,可以通过并行化来改进。

所用时间(N=2000,p=1):7.22s
耗时(N=2000,p=2):3.78s
耗时(N=2000,p=4):3.08s

但是,如果您在内存受限问题上使用 open-MP,您将永远不会有任何收获。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多