【问题标题】:OpenMP C++ Matrix Multiplication run slower in parallelOpenMP C++ 矩阵乘法并行运行较慢
【发布时间】:2014-05-03 06:27:05
【问题描述】:

我正在学习使用 OpenMP 并行执行 for 循环的基础知识。

遗憾的是,我的并行程序运行速度比串行版本慢 10 倍。 我究竟做错了什么?我错过了一些障碍吗?

double **basicMultiply(double **A, double **B, int size) {
   int i, j, k;
   double **res = createMatrix(size);
   omp_set_num_threads(4);
   #pragma omp parallel for private(k)
   for (i = 0; i < size; i++) {
      for (j = 0; j < size; j++) {
         for (k = 0; k < size; k++) {
            res[i][j] += A[i][k] * B[k][j];
         }
      }
   }
   return res;
}

非常感谢!

【问题讨论】:

  • size 的值是多少,您尝试过代码吗?此外,如果您开始为其中一个指定 kj,则应将其标记为私有。
  • 你的矩阵有多大?
  • 尺寸 = 512;我觉得够大了吧?
  • 您是否像@rerx 所说的那样将jk 变量设为私有?
  • 因为这是 C++,你应该使用混合声明。那么你永远不会有这个问题 for(int i=0...) for(int j=0...).

标签: c++ openmp matrix-multiplication


【解决方案1】:

如果size 很小,线程同步的开销将影响并行计算带来的任何性能提升。

【讨论】:

    【解决方案2】:

    您的问题是由于内部循环变量j 的竞争条件造成的。它需要私有化。

    对于 C89,我会这样做:

    #pragma omp parallel
    {
        int i, j, k;
        #pragma omp for
        for(i=0; ...
    

    对于 C++ 或 C99,使用混合声明

    #pragma omp parallel for
    for(int i=0; ...
    

    这样做您不必显式声明任何共享或私有内容。

    对您的代码进行一些进一步的修改。当您执行B[k][j] 时,您的单线程代码对缓存不友好。这会读取一个高速缓存行,然后移动到下一个高速缓存行,依此类推,直到完成点积,此时其他高速缓存行已被逐出。相反,您应该先进行转置并以BT[j][k] 访问。此外,您分配了数组数组,而不是一个连续的二维数组。我修复了您的代码以使用转置和连续的二维数组。

    这是我得到 size=512 的时间。

    no transpose  no openmp 0.94s
    no transpose, openmp    0.23s
    tranpose, no openmp     0.27s
    transpose, openmp       0.08s
    

    下面是代码(另见http://coliru.stacked-crooked.com/a/ee174916fa035f97

    #include <stdio.h>
    #include <stdlib.h>
    #include <omp.h>
    
    void transpose(double *A, double *B, int n) {
        int i,j;
        for(i=0; i<n; i++) {
            for(j=0; j<n; j++) {
                B[j*n+i] = A[i*n+j];
            }
        }
    }
    
    void gemm(double *A, double *B, double *C, int n) 
    {   
        int i, j, k;
        for (i = 0; i < n; i++) { 
            for (j = 0; j < n; j++) {
                double dot  = 0;
                for (k = 0; k < n; k++) {
                    dot += A[i*n+k]*B[k*n+j];
                } 
                C[i*n+j ] = dot;
            }
        }
    }
    
    void gemm_omp(double *A, double *B, double *C, int n) 
    {   
        #pragma omp parallel
        {
            int i, j, k;
            #pragma omp for
            for (i = 0; i < n; i++) { 
                for (j = 0; j < n; j++) {
                    double dot  = 0;
                    for (k = 0; k < n; k++) {
                        dot += A[i*n+k]*B[k*n+j];
                    } 
                    C[i*n+j ] = dot;
                }
            }
    
        }
    }
    
    void gemmT(double *A, double *B, double *C, int n) 
    {   
        int i, j, k;
        double *B2;
        B2 = (double*)malloc(sizeof(double)*n*n);
        transpose(B,B2, n);
        for (i = 0; i < n; i++) { 
            for (j = 0; j < n; j++) {
                double dot  = 0;
                for (k = 0; k < n; k++) {
                    dot += A[i*n+k]*B2[j*n+k];
                } 
                C[i*n+j ] = dot;
            }
        }
        free(B2);
    }
    
    void gemmT_omp(double *A, double *B, double *C, int n) 
    {   
        double *B2;
        B2 = (double*)malloc(sizeof(double)*n*n);
        transpose(B,B2, n);
        #pragma omp parallel
        {
            int i, j, k;
            #pragma omp for
            for (i = 0; i < n; i++) { 
                for (j = 0; j < n; j++) {
                    double dot  = 0;
                    for (k = 0; k < n; k++) {
                        dot += A[i*n+k]*B2[j*n+k];
                    } 
                    C[i*n+j ] = dot;
                }
            }
    
        }
        free(B2);
    }
    
    int main() {
        int i, n;
        double *A, *B, *C, dtime;
    
        n=512;
        A = (double*)malloc(sizeof(double)*n*n);
        B = (double*)malloc(sizeof(double)*n*n);
        C = (double*)malloc(sizeof(double)*n*n);
        for(i=0; i<n*n; i++) { A[i] = rand()/RAND_MAX; B[i] = rand()/RAND_MAX;}
    
        dtime = omp_get_wtime();
        gemm(A,B,C, n);
        dtime = omp_get_wtime() - dtime;
        printf("%f\n", dtime);
    
        dtime = omp_get_wtime();
        gemm_omp(A,B,C, n);
        dtime = omp_get_wtime() - dtime;
        printf("%f\n", dtime);
    
        dtime = omp_get_wtime();
        gemmT(A,B,C, n);
        dtime = omp_get_wtime() - dtime;
        printf("%f\n", dtime);
    
        dtime = omp_get_wtime();
        gemmT_omp(A,B,C, n);
        dtime = omp_get_wtime() - dtime;
        printf("%f\n", dtime);
    
        return 0;
    
    }
    

    【讨论】:

    • 非常感谢,这太棒了! :)
    • rand()/RAND_MAX 为零。
    • @Kadir 将其更改为 1.0*rand()/RAND_MAX
    • @Zboson,嗨,我将您的代码(符合 g++)与 Matlab 进行了比较。你的输出是 0.457343、0.161412、0.281850 和 0.105735。但 Matlab 仅在 0.002953 秒内完成了这项工作。您知道如何使用 C 达到 Matlab 性能吗?谢谢。
    • @user153245,是的,您需要进行循环平铺/阻塞以更好地利用缓存。如果你这样做,你可能会得到大约 50% 的 Matlab。不过要像 Matlab 一样好是非常困难的。
    【解决方案3】:

    另外。 “Z boson”,我用英特尔 i5(2 个物理内核或 4 个逻辑内核)在笔记本电脑上测试了你的 C 代码。不幸的是,计算速度不是很快。对于 2000x2000 随机双精度矩阵,我获得了以下结果(使用 VS 2010 和 OpenMP 2.0):

    为 Win64 编译:C = A*B,其中 A,B 是大小为 (2000x2000) 的矩阵:

    最大线程数 = 4
    创建随机矩阵:= 0.303555 s
    没有转置没有 openmp = 100.539924 s
    没有转置,openmp = 47.876084 s
    转置,没有 openmp = 27.872169 s
    转置,openmp = 15.821010 s

    为 Win32 编译:C = A*B,其中 A,B 是大小为 (2000x2000) 的矩阵:

    最大线程数 = 4
    创建随机矩阵:= 0.378804 s
    没有转置没有 openmp = 98.613992 s
    没有转置,openmp = 48.233655 s
    转置,没有 openmp = 29.590350 s
    转置,openmp = 13.678097 秒

    请注意,对于“Hynek Blaha”代码,我的系统上的计算时间是 739.208s226.62s 使用 openMP)!

    而在 Matlab x64 中:

    n = 2000; 
    A = rand(n); B = rand(n);
    
    tic
    C = A*B;
    toc
    

    计算时间为0.591440秒

    但使用 openBLAS 包我达到了 0.377814 秒 的速度(使用带有 openMP 4.0 的 minGW)。 Armadillo 包为矩阵运算与 openBLAS(或其他类似包)的连接提供了一种简单的方法(在我看来)。在这种情况下,代码是

    #include <iostream>
    #include <armadillo>
    using namespace std;
    using namespace arma;
    
    int main(){
        int n = 2000;
        int N = 10; // number of repetitions
        wall_clock timer;
    
        arma_rng::set_seed_random();
    
        mat A(n, n, fill::randu), B(n, n, fill::randu);
    
        timer.tic();
        // repeat simulation N times
        for(int n=1;n<N;n++){
          mat C = A*B;
        }
        cout << timer.toc()/double(N) << "s" << endl;
    
        return 0;
    }
    

    【讨论】:

    • 这是一个很好的例子!我目前正在努力使用 OpenMP,即使仅设置大型矩阵的所有值,我的性能也很差。你能看看我的问题吗?任何建议将不胜感激! stackoverflow.com/questions/40700927/…
    • 关于 MATLAB 时间的小评论。自本世纪初以来,MATLAB 将 MKL (LAPACK) 用于 LA 和矩阵计算。您可以使用version -blas 查看 MATLAB 的 BLAS 版本。
    猜你喜欢
    • 2013-05-18
    • 1970-01-01
    • 2017-09-14
    • 2018-08-24
    • 2018-12-05
    • 2012-05-30
    • 2020-11-28
    • 2021-07-20
    • 1970-01-01
    相关资源
    最近更新 更多