在xcode上增加线程数会增加编程时间

时间:2019-03-06 16:33:06

标签: c multithreading openmp

我是openmp的新手,目前正在尝试在Mac上的xcode中并行矩阵乘法。

我得到的结果很奇怪,因为它增加了我的程序时间而不是减少了程序时间。我的猜测是它正在发生,因为它仅使用一个内核而不使用其他内核,这是我的代码:

omp_set_num_threads(4);
#pragma omp parallel for private(i,j,k)
for (i=0; i<n; ++i) {
        for (j=0; j<n; ++j) {
               for (k=0; k<n; ++k) {
                    c[i][j] += a[i][k] * b[k][j];
               }
        } 
}
在两个具有1个线程的400 * 400矩阵上的

我得到551毫秒,2个线程421和3个线程678,并且随着我增加我的线程而增加。

任何想法我在做什么错或应该怎么办?!

3 个答案:

答案 0 :(得分:1)

您使用 bad 方法将矩阵相乘。 ijk算法生成许多高速缓存未命中。看你的内循环。每当索引k更改时,您就转到矩阵b的新行,而不是沿行使用缓存友好的遍历。而且由于缓存一致性算法,大量的缓存未命中会降低性能,并且对于并行代码而言还比较幼稚。 ikj算法(请参见下面的代码)要好得多。矩阵都是行主行的,不会产生缓存未命中。

我尝试试验您的代码。

为了保持稳定的时序,我对10个矩阵乘法循环进行了计时,并做了10次,并保持了最短的时间。

根据定义,可以选择ijk或ikj并控制并行度。

另一个定义选择并行或顺序版本。

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

int main() {
    double a[400][400], b[400][400], c[400][400] = { { 0.0 } };
    int i, j, k, n = 400;

    double t1, t2,t;

    srand(100); // better be deterministic when benchmarking
    for (i = 0; i < n; ++i) {
        for (j = 0; j < n; ++j) {
            a[i][j] = rand() / (double) RAND_MAX;
            b[i][j] = rand() / (double) RAND_MAX;
        }
    }

    t=1E100;
    for(int ll=0;ll<10;ll++){      
      t1 = omp_get_wtime();
      for(int mm=0;mm<10;mm++){
#if THREADS>1
#pragma omp parallel for private(i,j,k) num_threads(THREADS)
#endif
#ifdef ijk
        for (i=0; i<n; ++i) {
          for (j=0; j<n; ++j) {
            for (k=0; k<n; ++k) {
              c[i][j] += a[i][k] * b[k][j];
            }
          } 
        }
#else // ikj matrix multiplication
        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];
            }
          } 
        }
#endif      
      }
      t2 = omp_get_wtime();
      if (t>t2-t1) t=t2-t1;
    }

    printf("%g\n",t);

    // to fool these smart optimizers, do something with c
    FILE* devnull=fopen("/dev/null","w");
    fprintf(devnull,"%g\n",c[0][0]);
    return EXIT_SUCCESS;
}

现在进行实验:

首先以ijk

am@Mandel$ cc -fopenmp -O3 -march=native -DTHREADS=0 -Dijk omp2.c; ./a.out
0.196313
am@Mandel$ cc -fopenmp -O3 -march=native -DTHREADS=4 -Dijk omp2.c; ./a.out
0.293023

我们发现并行版本的速度要慢50%。

现在我们切换到ikj

am@Mandel$ cc -fopenmp -O3 -march=native -DTHREADS=0 -Uijk omp2.c; ./a.out
0.114659
am@Mandel$ cc -fopenmp -O3 -march=native -DTHREADS=4 -Uijk omp2.c; ./a.out
0.06113

现在顺序代码比顺序代码快大约两倍,并行版本比顺序代码快大约两倍。

可能使用较大的矩阵可以提高并行代码的效率。

答案 1 :(得分:0)

  

任何想法我在做什么错或应该怎么办?!

就您的代码而言,您似乎并没有做任何特别错误的事情。但是,多线程在软件级别和硬件级别都会带来开销。结果,对一个问题应用更多的线程并不能总是加快整体计算的速度,并且可能减慢其速度。如何影响特定任务取决于该任务以及主机体系结构和环境的详细信息。

不过,请考虑围绕您的示例代码构建的完整测试程序:

#include <stdlib.h>

int main() {
    double a[400][400], b[400][400], c[400][400] = { { 0.0 } };
    int i, j, k, n = 400;

    srand(time(NULL));
    for (i = 0; i < n; ++i) {
        for (j = 0; j < n; ++j) {
            a[i][j] = rand() / (double) RAND_MAX;
            b[i][j] = rand() / (double) RAND_MAX;
        }
    }

    #pragma omp parallel for private(i,j,k) num_threads(4)
    for (i=0; i<n; ++i) {
        for (j=0; j<n; ++j) {
           for (k=0; k<n; ++k) {
               c[i][j] += a[i][k] * b[k][j];
           }
        } 
    }

    return EXIT_SUCCESS;
}

我在num_threads构造上使用parallel for子句,而不是调用omp_set_numThreads()来设置请求的线程数,但是否则OMP区域与您的相同。在我自己的Linux系统上,通过time命令对此时间进行了粗略的计时,我发现经过的时间随着线程数量的减少而减少,表现出近乎线性的加速,直到大约四个线程。在那之后,第五个线程的速度稍有提高,经过时间和总CPU时间都从六个线程开始增加。

这些结果可能与以下事实有关:我的机器具有六个物理CPU内核,并且操作系统和各种后台进程始终会消耗一定数量的CPU资源。您的结果没有遵循相同的模式可能与程序的其余部分,环境或特定的OpenMP实现有关。它甚至可能与您如何运行计时赛有关。这不是因为您介绍的OpenMP区域存在缺陷。

答案 2 :(得分:0)

您的大错误是尝试并行化矩阵乘法。不是因为这不可能,而是因为它已经完成了(以及其他重要的优化措施,例如缓存阻止和向量化,您很可能无法解决)。

记住关键词:“最好的代码是我不必编写的代码”:-)

因此,除非您的目标是教育自己,否则请停止这样做。而是找到一个好的BLAS库,并花时间学习如何使用它。

Intel MKL对每个人都是免费的,这是一个合理的选择,但是Google可以为您找到许多其他选择)。

完全公开:我为英特尔工作,但不在MKL工作。