MPI實現矩陣乘法的Cannon算法

1. 實驗內容與方法

1) 對矩陣進行分塊,之後按塊進行乘法,使用的是cannon算法。Cannon算法是一種存儲有效的算法。爲了使兩矩陣下標滿足相乘的要求,它和並行分塊乘法不同,不是僅僅讓B矩陣的各列塊循環移動,而是有目的地讓A的各行塊以及B的各列塊皆施行循環移位,從而實現對C的子塊的計算。將矩陣AB分成p個方塊AijBij,每塊大小爲,並將它們分配給個處理器。開始時處理器Pij存放塊AijBij,並負責計算塊Cij,然後算法開始執行:

⑴將塊Aij向左循環移動i步;將塊Bij向上循環移動j步;

Pij執行乘加運算後將塊Aij向左循環移動1步,塊Bij向上循環移動1步;

⑶重複第⑵步,總共執行次乘加運算和次塊AijBij的循環單步移位。

2) 初始化數組。使用vector定義矩陣A、B和C,設置矩陣大小均爲6000 * 6000。使用srand((unsigned) time(NULL))設置使用系統定時器的值作爲隨機種子,隨機初始化矩陣A和矩陣B的值。

3) 程序計時。使用MPI自帶的MPI_Wtime()函數,MPI_Wtime()記錄的是牆上時間,在CPU時間的基礎上包括了空閒等待時間。在各進程計算過程分塊矩陣C之前和之後使用MPI_Wtime()函數計時,不計算最開始的初始化過程MPI發送的時間和最後接收數據彙總成矩陣C的時間。

2. 運行時間

矩陣A、B和C的大小均爲6000 * 6000,且。經過代碼運行計時,得到以下的實驗結果表格。

處理器個數

用時/秒

串行

1734.048241

4核

321.548999

9核

194.883003

16核

111.360064

25核

61.248356

36核(3 * 12)

35.795692

 

3. 加速比

處理器個數

加速比

串行

1

4核

5.39279627

9核

8.89789368

16核

15.5715449

25核

28.3117516

36核(3 * 12)

48.4429311

4. 實驗分析

從實驗結果和加速比可以看出:

1 隨着核數的增加,矩陣乘法所需要的時間在相應縮短;

2 矩陣乘法的加速比與運行的核心數近似成正比;

3 核心數越多,每個核分到的分塊矩陣就越小,每個覈計算速度就越快,加速比比核心數更高。

5. 源代碼

#include "iostream"
#include "cstdlib"
#include "cstring"
#include "mpi.h"
#include "ctime"
#include "cmath"
#include "algorithm"
#include "vector"

using namespace std;
#define N 6000 //n是矩陣大小

// 全局變量聲明
int block, block_size, p, sp;//, block是塊的大小,block_size是塊元素個數,等於block*block,p是處理器個數,sp是sqrt(p)
vector<vector<double>> A(N, vector<double>(N, 0)), B(N, vector<double>(N, 0)), C(N, vector<double>(N, 0));
double *a, *b, *c, *temp_a, *temp_b;
int my_rank, my_row, my_col;// my_rank是處理器的id,(my_row, my_col)是處理器邏輯陣列座標
MPI_Status status;

double start_time, end_time;

int get_index(int row, int col, int sp);//處理器邏輯陣列座標到rank的轉換
void init_Matrix();//隨機生成矩陣A和B
void scatter_A_B();//id爲0的處理器向其他處理器發送矩陣A、B的分塊
void init_alignment();//矩陣A和B的初始化,即A每行不同程度的左移,B每行不同程度的上移
void shift();//矩陣A每行左移一位,矩陣B每行上移一位,同時計算分塊的中間結果C
void collect_C();//id爲0的處理器從其餘處理器收集分塊矩陣C
void serial_Multiplication();//矩陣串行的計算

int main() {
    MPI_Init(NULL, NULL);
    MPI_Comm_size(MPI_COMM_WORLD, &p);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);
    
    sp = sqrt(p);
    
    if (sp * sp != p) {
        if (my_rank == 0)
            cout << "not a quadratic number" << endl;
        MPI_Finalize();
        exit(1);
    }
    
    block = N / sp;
    block_size = block * block;
    
    my_col = my_rank % sp;
    my_row = my_rank / sp;
    
    a = (double *) malloc(block_size * sizeof(double));
    b = (double *) malloc(block_size * sizeof(double));
    c = (double *) malloc(block_size * sizeof(double));;
    
    for (int i = 0; i < block_size; ++i) {
        c[i] = 0;
    }
    temp_a = (double *) malloc(block_size * sizeof(double));
    temp_b = (double *) malloc(block_size * sizeof(double));
    
    if (my_rank == 0) {
        init_Matrix();
        scatter_A_B();
    } else {
        MPI_Recv(a, block_size, MPI_FLOAT, 0, 1, MPI_COMM_WORLD, &status);
        MPI_Recv(b, block_size, MPI_FLOAT, 0, 2, MPI_COMM_WORLD, &status);
    }
    start_time = MPI_Wtime();
    init_alignment();
    shift();
    end_time = MPI_Wtime();
    
    if (my_rank == 0) {
        cout << "matrix size : " << N << " * " << N << endl;
        printf("time = %lf\n", end_time - start_time);
    }
    if (my_rank == 0) {
        collect_C();
    } else {
        MPI_Send(c, block_size, MPI_FLOAT, 0, 1, MPI_COMM_WORLD);
    }
    MPI_Barrier(MPI_COMM_WORLD);
    MPI_Finalize();
    
    return 0;
}

/*
 處理器邏輯陣列座標到rank的轉換
 輸入:座標(row, col),sqrt(p)
 輸出:rank
 */
int get_index(int row, int col, int sp) {
    return ((row + sp) % sp) * sp + (col + sp) % sp;
}

/*
 隨機生成矩陣A和B
 */
void init_Matrix() {
    srand((unsigned int) time(NULL)); //設置隨機數種子
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            A[i][j] = rand();
            B[i][j] = rand();
        }
    }
}

/*
 id爲0的處理器向其他處理器發送矩陣A、B的分塊
 */
void scatter_A_B() {
    int p_i_min, p_j_min, p_i_max, p_j_max;
    for (int k = 0; k < p; k++) {
        p_j_min = (k % sp) * block;
        p_j_max = (k % sp + 1) * block - 1;
        p_i_min = k / sp * block;
        p_i_max = (k / sp + 1) * block - 1;
        // id爲0的處理器將矩陣A和B中的數據轉換成一位數組傳到temp中
        for (int i = p_i_min; i <= p_i_max; ++i) {
            for (int j = p_j_min; j <= p_j_max; j++) {
                temp_a[(i - p_i_min) * block + j - p_j_min] = A[i][j];
                temp_b[(i - p_i_min) * block + j - p_j_min] = B[i][j];
            }
        }
        if (k == 0) { // id爲0的處理器直接拷貝過去,其他處理器則發送過去
            memcpy(a, temp_a, block_size * sizeof(double));
            memcpy(b, temp_b, block_size * sizeof(double));
        } else {
            MPI_Send(temp_a, block_size, MPI_FLOAT, k, 1, MPI_COMM_WORLD);
            MPI_Send(temp_b, block_size, MPI_FLOAT, k, 2, MPI_COMM_WORLD);
        }
    }
}

/*
 將A中座標爲(i, j)的分塊循環左移i步
 將B中座標爲(i, j)的分塊循環上移j步
 */
void init_alignment() {
    MPI_Sendrecv(a, block_size, MPI_FLOAT, get_index(my_row, my_col - my_row, sp), 1, temp_a, block_size, MPI_FLOAT,
                 get_index(my_row, my_col + my_row, sp), 1, MPI_COMM_WORLD, &status);
    memcpy(a, temp_a, block_size * sizeof(double));
    MPI_Sendrecv(b, block_size, MPI_FLOAT, get_index(my_row - my_col, my_col, sp), 1, temp_b, block_size, MPI_FLOAT,
                 get_index(my_row + my_col, my_col, sp), 1, MPI_COMM_WORLD, &status);
    memcpy(b, temp_b, block_size * sizeof(double));
}

/*
 矩陣A每行左移一位,矩陣B每行上移一位,同時計算分塊的中間結果C
 */
void shift() {
    double total = 0;
    for (int l = 0; l < sp; l++) {
        for (int i = 0; i < block; i++) {
            for (int j = 0; j < block; j++) {
                total = c[i * block + j];
                for (int k = 0; k < block; k++) {
                    total += a[i * block + k] * b[k * block + j];
                }
                c[i * block + j] = total;
            }
            
        }
        // 將A全部循環左移1位
        MPI_Sendrecv(a, block_size, MPI_FLOAT, get_index(my_row, my_col - 1, sp), 1, temp_a, block_size, MPI_FLOAT,
                     get_index(my_row, my_col + 1, sp), 1, MPI_COMM_WORLD, &status);
        memcpy(a, temp_a, block_size * sizeof(double));
        // 將B全部循環上移1位
        MPI_Sendrecv(b, block_size, MPI_FLOAT, get_index(my_row - 1, my_col, sp), 1, temp_b, block_size, MPI_FLOAT,
                     get_index(my_row + 1, my_col, sp), 1, MPI_COMM_WORLD, &status);
        memcpy(b, temp_b, block_size * sizeof(double));
    }
}

/*
 id爲0的處理器從其餘處理器收集分塊矩陣C
 */
void collect_C() {
    int p_i_min, p_j_min, p_i_max, p_j_max;
    // 將id爲0的處理器的分塊矩陣c結果賦給C對應位置
    for (int i = 0; i < block; i++) {
        for (int j = 0; j < block; j++) {
            C[i][j] = c[i * block + j];
        }
    }
    for (int k = 1; k < p; k++) {
        MPI_Recv(c, block_size, MPI_FLOAT, k, 1, MPI_COMM_WORLD, &status);
        p_j_min = (k % sp) * block;
        p_j_max = (k % sp + 1) * block - 1;
        p_i_min = k / sp * block;
        p_i_max = (k / sp + 1) * block - 1;
        for (int i = p_i_min; i <= p_i_max; i++) {
            for (int j = p_j_min; j <= p_j_max; ++j) {
                C[i][j] = c[(i - p_i_min) * block + j - p_j_min];
            }
        }
    }
}

void serial_Multiplication();//矩陣串行的計算  使用1個線程即串行的計算

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章