OpenMP用于矩阵乘法

OpenMP for matrix multiplication

本文关键字:用于 OpenMP      更新时间:2023-10-16

我是OpenMP新手,正在拼命学习。我试图在visual studio 2012中编写c++示例代码来实现矩阵乘法。我希望有OpenMP经验的人可以看看这段代码,并帮助我获得最终的速度/并行化:

#include <iostream>
#include <stdlib.h>
#include <omp.h>
#include <random>
using namespace std;
#define NUM_THREADS 4
// Program Variables
double**        A;
double**        B;
double**        C;
double          t_Start;
double          t_Stop;
int             Am;
int             An;
int             Bm;
int             Bn;
// Program Functions
void            Get_Matrix();
void            Mat_Mult_Serial();
void            Mat_Mult_Parallel();
void            Delete_Matrix();

int main()
{
    printf("Matrix Multiplication Programnn");
    cout << "Enter Size of Matrix A: ";
    cin >> Am >> An;
    cout << "Enter Size of Matrix B: ";
    cin >> Bm >> Bn;
    Get_Matrix();
    Mat_Mult_Serial();
    Mat_Mult_Parallel();

    system("pause");
    return 0;
}

void Get_Matrix()
{
    A = new double*[Am];
    B = new double*[Bm];
    C = new double*[Am];
    for ( int i=0; i<Am; i++ ){A[i] = new double[An];}
    for ( int i=0; i<Bm; i++ ){B[i] = new double[Bn];}
    for ( int i=0; i<Am; i++ ){C[i] = new double[Bn]; }
    for ( int i=0; i<Am; i++ )
    {
         for ( int j=0; j<An; j++ )
         {
             A[i][j]= rand() % 10 + 1;
         }
    }
    for ( int i=0; i<Bm; i++ )
    {
        for ( int j=0; j<Bn; j++ )
        {
            B[i][j]= rand() % 10 + 1;
        }
    }
    printf("Matrix Create Complete.n");
}

void Mat_Mult_Serial()
{
    t_Start = omp_get_wtime();
    for ( int i=0; i<Am; i++ )
    {
        for ( int j=0; j<Bn; j++ )
        {
            double temp = 0;
            for ( int k=0; k<An; k++ )
            {
                temp += A[i][k]*B[k][j];
            }
        }
    }
    t_Stop = omp_get_wtime() - t_Start;
    cout << "Serial Multiplication Time: " << t_Stop << " seconds" << endl;
    }

void Mat_Mult_Parallel()
{
    int i,j,k;
    t_Start = omp_get_wtime();
    omp_set_num_threads(NUM_THREADS);
    #pragma omp parallel for private(i,j,k) schedule(dynamic)
    for ( i=0; i<Am; i++ )
    {
        for ( j=0; j<Bn; j++ )
        {
            //double temp = 0;
            for ( k=0; k<An; k++ )
            {
                C[i][j] += A[i][k]*B[k][j];
            }
        }
    }
    t_Stop = omp_get_wtime() - t_Start;
    cout << "Parallel Multiplication Time: " << t_Stop << " seconds." << endl;
}

void Delete_Matrix()
{
    for ( int i=0; i<Am; i++ ){ delete [] A[i]; }
    for ( int i=0; i<Bm; i++ ){ delete [] B[i]; }
    for ( int i=0; i<Am; i++ ){ delete [] C[i]; }
    delete [] A;
    delete [] B;
    delete [] B;
}

我的例子是基于我为并行教学创建的矩阵类。如果你感兴趣,请随时与我联系。有几种方法可以加速矩阵乘法:

<<h3>存储/h3>

使用按行主序排列的一维数组,以便更快地访问元素。
可以用A[i * An + j]

访问A(i,j)

使用循环不变优化

for (int i = 0; i < m; i ++)
    for (int j = 0; j < p; j ++)
    {
        Scalar sigma = C(i, j);
        for (int k = 0; k < n; k ++)
            sigma += (*this)(i, k) * B(k, j);
        C(i, j) = sigma;
    }

这可以防止在最内部的循环中多次重新计算C(i,j)。

改变循环顺序"for k <-> for i"

for (int i = 0; i < m; i ++)
    for (int k = 0; k < n; k ++)
    {
        Aik = (*this)(i, k);
        for (int j = 0; j < p; j ++)
            C(i, j) += Aik * B(k, j);
    }

这允许使用空间数据局部性

使用循环阻塞/平铺

for(int ii = 0; ii < m; ii += block_size)
    for(int jj = 0; jj < p; jj += block_size)
        for(int kk = 0; kk < n; kk += block_size)
            #pragma omp parallel for // I think this is the best place for this case
            for(int i = ii; i < ii + block_size; i ++)
                for(int k = kk; k < kk + block_size; k ++)
                {
                    Scalar Aik = (*this)(i, k);
                    for(int j = jj; j < jj + block_size; j ++)
                        C(i, j) +=  Aik * B(k, j);
                }

这可以使用更好的时间数据局部性。最佳的block_size取决于你的体系结构和矩阵大小。

然后并行化!

一般来说,#pragma omp parallel for应该在最外层的循环中执行。也许在前两个外部环路上使用两个平行环路可以得到更好的结果。这取决于你使用的架构,矩阵大小……你必须测试!由于矩阵乘法具有静态工作负载,因此我将使用静态调度。

莫尔优化!

你可以做循环巢优化。您可以对代码进行矢量化。你可以看看BLAS是怎么做的。

我对OpenMP很陌生,这段代码很有指导意义。然而,我在串行版本中发现了一个错误,这使得它比并行版本具有不公平的速度优势。

不像在并行版本中那样写C[i][j] += A[i][k]*B[k][j];,而是在串行版本中写temp += A[i][k]*B[k][j];。这要快得多(但不能帮助您计算C矩阵)。所以你不是在比较苹果和苹果,这使得并行代码看起来比较慢。当我修复这一行并在我的笔记本电脑上运行它(它允许2个线程)时,并行版本的速度几乎是两倍。不坏!