【问题标题】:OpenMP Performance Issues with Matrix Multiplication矩阵乘法的 OpenMP 性能问题
【发布时间】:2016-01-25 03:02:48
【问题描述】:

我在使用 OpenMp 时遇到性能问题。我正在尝试测试不使用 OpenMP 的单线程程序和使用 OpenMP 的应用程序的结果。通过在线查看比较矩阵链乘法程序的结果,openMP 实现的速度是前者的 2 到 3 倍,但我的实现对于两个应用程序的速度是相同的。我实施 openMP 的方式不正确吗?关于 openMP 的任何指针以及如何正确实现它?任何帮助深表感谢。提前致谢。

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

int main( int argc , char *argv[] ) 
{
   srand(time(0));
   if ( argc != 2 )
   {
      printf("Usage: %s <size of nxn matrices>\n", argv[0]);
      return 1; 
   }

   int n = atoi( argv[1] );
   int a, b;
   double A[n][n], B[n][n], C[n][n];
   FILE *fp;
   fp = fopen("/home/mkj0002/CPE631/Homework2/ArrayTry/matrixResults", "w+"); //For the LeCASA machine

   for(a = 0; a < n; a++)
   {
       for(b = 0; b < n; b++)
       {
          A[a][b] = ((double)rand()/(double)RAND_MAX);  //Number between 0 and 1
          A[a][b] = (double)rand();         //Number between 0 and RAND_MAX
          B[a][b] = ((double)rand()/(double)RAND_MAX);  //Number between 0 and 1
          B[a][b] = (double)rand();         //Number between 0 and RAND_MAX
          C[a][b] = 0.0;
       }
    }

    #pragma omp parallel shared(A,B,C)
    {
        int i,j,k;
        #pragma omp for schedule(guided,n)
        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;
                fprintf(fp,"0.4lf",C[i][j]);
            }
        }
    }

    if(fp)
    {
        fclose(fp);
    }
    fp = NULL;

    return 0;
}                  

【问题讨论】:

    标签: c openmp


    【解决方案1】:

    (1) 不要在并行区域内执行 I/O。当您将其移出并将许多 C 变量同时写入文件时,您会看到即时加速。

    (2) 完成上述操作后,您应该将调度更改为static,因为每个循环将执行完全相同的计算量,并且不再需要因花哨的调度而产生开销。

    (3) 此外,为了更好地利用缓存,您应该交换 jk 循环。要看到这一点,请想象在当前循环中仅访问您的 B 变量。

    for(j = 0; j < n; ++j)
    {
        for(k = 0; k < n; ++k)
        {
            B[k][j] += 5.0;
        }
    }
    

    您可以看到它是如何访问 B 的,就好像它以 Fortran 的列优先格式存储一样。更多信息可以找到here。更好的选择是:

    for(k = 0; k < n; ++k)
    {
        for(j = 0; j < n; ++j)
        {
            B[k][j] += 5.0;
        }
    }
    

    回到你的例子,我们仍然需要处理sum 变量。一个简单的建议是存储您正在计算的当前sums 行,然后在完成当前循环后将它们全部保存。

    结合所有 3 个步骤,我们得到如下结果:

    #pragma omp parallel shared(A,B,C)
    {
        int i,j,k;
        double sum[n]; // one for each j
    
        #pragma omp for schedule(static)
        for(i = 0; i < n; ++i)
        {
            for(j = 0; j < n; ++j)
                sum[j] = 0;
    
            for(k = 0; k < n; ++k)
            {
                for(j = 0; j < n; ++j)
                {
                    sum[j] += A[i][k] * B[k][j];
                }
            }
    
            for(j = 0; j < n; ++j)
                C[i][j] = sum[j];
        }
    }
    
    // perform I/O here using contiguous blocks of C variable
    

    希望对您有所帮助。

    编辑:根据@Zboson 的建议,在整个程序中完全删除sum[j] 并用C[i][j] 替换它会更容易。

    【讨论】:

    • 交换jk 循环除了我最近才想到的更好的缓存使用之外还有另外两个主要优点。首先它没有依赖链,所以你不需要展开循环。其次,因为它没有依赖链,所以您也不需要使用关联数学来向量化循环,即您只需要 -O3 而不是 -Ofastomp simd
    • 顺便说一句,你为什么使用sum[j]?为什么不直接使用C[i][j] += A[i][k] * B[k][j]
    • @Zboson 你能进一步解释什么是依赖链以及它与循环展开的关系吗?或者提供链接?我从未听说过在软件依赖项之外的任何地方使用过这个术语。
    • @Zboson 另外,现在是凌晨 3 点。你是对的 - 用 C[i][j] 替换 sum[j] 也一样好。我只是在复制 OP 的结构,并没有完全考虑清楚。
    • 对于依赖链,请参阅 Agner Fog 的Optimizing C++ manual 的第 11 节(目前为第 103-105 页),特别是示例 11.2a 和 11.2b。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-15
    • 1970-01-01
    • 2015-12-26
    • 2013-05-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多