nt實驗報告之多核環境下OpenMP並行編程

多核環境下OpenMP並行編程

一、實驗環境

操作系統:Windows 10、Linux(Centos 7 虛擬機)

運行環境:Visual Stdudio 2019(cl)、Vim(g++)、VMware Workstation Pro

CPU處理器:AMD Zen2 3700x @3.8Ghz 8c16t ,4c4t(虛擬機)

二、實驗內容

學習編制多線程並行程序實現如下功能:

1.創建多線程,輸出線程號和線程數。

2.學習for多線程並行。

3.學習while多線程並行,實現全局共享變量存取。

4.編程實現大規模向量的並行計算

三、實驗目的

1.掌握OpenMP並行編程基礎;

2.掌握在Linux平臺上編譯和運行OpenMP程序;

3.掌握在Windows平臺上編譯和運行OpenMP程序。

4.用OpenMP實現最基本的矩陣乘法以及性能分析

四、實驗步驟

4.1 Windos下編譯並運行OpenMP程序

4.1.1 環境配置

在vs的(項目)(屬性)(配置屬性)(C/C++)(語言)中設置(OpenMP支持)爲(是),並將其中的符合模式設置爲(否)

在這裏插入圖片描述

4.1.2 代碼

#include <omp.h>
#include <stdio.h>
int main() {
	int nthreads, tid;
	omp_set_num_threads(8);
	#pragma omp parallel private(nthreads, tid)
	{
		tid = omp_get_thread_num();
		printf("Hello World from OMP thread %d\n", tid);
		if (tid == 0) {
			nthreads = omp_get_num_threads();
			printf("Number of threads is %d\n", nthreads);
		}
	}
}

4.1.3 運行結果

在這裏插入圖片描述

4.2 Linux平臺上編譯和運行OpenMP程序

4.2.1 環境配置:

安裝g++:yum install g++

編譯參數:g++ -fopenmp -o

測試代碼同4.1.2windows環境下的代碼

4.2.3 運行結果

在這裏插入圖片描述

4.3 多線程實現矩陣乘法

4.3.1 TimeCalculate() 計時函數

void TimeCalculate() {
    static bool is_record = 1;
    is_record = 1 - is_record;
    static clock_t TimeStart = clock();
    if (is_record == 0)
        TimeStart = clock();
    else {
        const clock_t TimeEnd = clock();
        std::cout << "This costs: ";
        std::cout << (double)(TimeEnd - TimeStart) / CLK_TCK * 1000;
        std::cout << " ms." << std::endl;
    }
}

通過static全局靜態變量,可以實現運行該函數開始計時,再次運行停止計時,並輸出時間花費。

4.3.2 Matrix 矩陣類和矩陣乘法

class Matrix {
public:
    int rows; // 行
    int cols; // 列
    float* elements;

    Matrix(int rows, int cols, float v) :rows(rows), cols(cols) {
        elements = new float[rows * cols];
        for (int i = 0; i < cols * rows; i++) elements[i] = v;
    }

    Matrix(int rows, int cols, float *v) :rows(rows), cols(cols) {
        elements = new float[rows * cols];
        for (int i = 0; i < cols * rows; i++) elements[i] = v[i];
    }
    
    ~Matrix() {
        delete[] elements;
    }
};

void MatMul(Matrix& A, Matrix& B, Matrix& ret) {
    for (int i = 0; i < A.rows; i++) {
        for (int j = 0; j < B.cols; j++) {
            float t = 0;
            for (int k = 0; k < A.cols; k++)
                t += A.elements[i * A.cols, k] * B.elements[k * B.cols + j];
            ret.elements[i * ret.cols + j] = t;
        }
    }
}

Matrix矩陣類提供了兩種構造方式,分別爲(行,列,元素值)和(行,列,元素值數組)

MatMul()函數傳入三個矩陣引用,分別是矩陣A、矩陣B和結果矩陣ret,通過三層循環得到結果矩陣ret

4.3.3 並行實現多次矩陣乘法

void test1(int t_num) {
    Matrix A(4, 4, 1), B(4, 4, 2), C(4, 4, 0.0);
    int per = 10000000 / t_num;
    omp_set_num_threads(t_num);
    int t, i;
    #pragma omp parallel for
    for (t = 0; t < t_num; t++) {
        for (i = 0; i < per; i++) {
            MatMul(A, B, C);
        }
    }
}

傳入參數爲使用的線程數t_num,該函數計算了1000萬次4*4的矩陣乘法,使用了t_num個線程進行並行,每個線程計算10000000 / t_num次矩陣乘法,當t_num等於1時,原函數相當於串行計算。

4.3.4 並行實現單次大矩陣乘法

重寫MatMul()函數

void MatMul2(Matrix& A, Matrix& B, Matrix& ret, int t_num) {
    omp_set_num_threads(t_num);
    #pragma omp parallel default(shared)
    {
        int id = omp_get_thread_num();
        for (int i = 0; i < A.rows; i++) {
            if (i % t_num != id) continue;
            for (int j = 0; j < B.cols; j++) {
                float t = 0;
                for (int k = 0; k < A.cols; k++)
                    t += A.elements[i * A.cols, k] * B.elements[k * B.cols + j];
                ret.elements[i * ret.cols + j] = t;
            }
        }
    }
}

MatMul2()函數傳入三個矩陣引用和線程數t_num,矩陣分別是矩陣A、矩陣B和結果矩陣ret,先調用omp_set_num_threads()初始化並行線程個數,通過最外層的同餘判斷,使得線程id計算第i%t_num==id輪循環,從而達到並行計算的效果。

void test2(int t_num) {
    Matrix A(2000, 2000, 3), B(2000, 2000, 2), C(2000, 2000, 0.0);
    MatMul2(A, B, C, t_num, A.cols * A.elements[0] * B.elements[0]);
}

傳入參數爲使用的線程數t_num,該函數計算通過調用MatMul2()函數,使用t_num個線程並行計算兩個2000*2000的矩陣。

五、性能分析

5.1 加速比

​ 加速比(speedup)是同一個任務在單處理器系統和並行處理器系統中運行消耗的時間的比率,用來衡量並行系統或程序並行化的性能和效果。

加速比SpS_p以如下公式定義:Sp=T1TpS_p=\frac{T_1}{T_p}

其中pp 指CPU數量,T1T_1指順序執行的執行時間,TpT_p指當有pp個處理器時,並行算法執行的時間

5.2 並行實現多次矩陣乘法性能

在這裏插入圖片描述
在這裏插入圖片描述

5.3 並行實現單次大矩陣乘法性能

在這裏插入圖片描述
在這裏插入圖片描述

5.4 性能分析

​ 通過上面四張圖我們可以看出隨着線程數的增加,運行時間先減小後增大,並在16線程時取到最小值;加速比先增大後減小,同樣在16線程時取到最大值。線程數量在1至16時,線程數量沒增加一倍,運行耗時和加速比變爲原來的二分之一多,加速比變爲原來的兩倍不到,而在超過16線程時,運行時間不再繼續下降,反而還會略有上升,加速比同理。

​ 可能的原因分析:上述實驗是在AMD Zen2 3700x @3.8Ghz 8c16t的硬件環境下完成的,這顆CPU共有8個物理核心,16個線程(下圖爲任務管理器截圖)

在這裏插入圖片描述

​ 因此在使用和處理器相同的16線程進行測試時,會取得最大的運行效率,而在線程數量超過16時,前16線程組線程並行,但其餘線程需要等待還未結束的線程讓出資源才能開始啓動,相較於前16線程是串行執行的,而線程的切換還需要耗費額外時間,因此反而可能不及僅有16線程的效率。

六、實現感想

​ 在本次實驗我學瞭解了OpenMP語句的基本語法和用法。相較於CUDA並行編程,OpenMP實現起來比較簡單,僅需要通過預編譯指令以及一些簡單的庫函數就可以完成複雜的並行計算,在如今處理器向多核發展的大環境下,並且帶來十分不錯的計算效率和加速比。此外要注意在OpenMP的編程過程中,使用的線程數量不宜超過實體CPU的線程數量,否則會帶來效率上的下降。最後也不是所有程序都需要用到並行計算,部分小規模運算使用並行反而會增大開銷,而另一部分則只能通過串行來解決。

七:附錄(完整測試代碼)

#include <omp.h> // OpenMP編程需要包含的頭文件
#include <bits/stdc++.h>

using namespace std;

void TimeCalculate() {
    static bool is_record = 1;
    is_record = 1 - is_record;
    static clock_t TimeStart = clock();
    if (is_record == 0)
        TimeStart = clock();
    else {
        const clock_t TimeEnd = clock();
        std::cout << "This costs: ";
        std::cout << (double)(TimeEnd - TimeStart) / CLK_TCK * 1000;
        std::cout << " ms." << std::endl;
    }
}

class Matrix {
public:
    int rows; // 行
    int cols; // 列
    double* elements;

    Matrix(int rows, int cols, double v) :rows(rows), cols(cols) {
        elements = new double[rows * cols];
        for (int i = 0; i < cols * rows; i++) elements[i] = v;
    }

    Matrix(int rows, int cols, double *v) :rows(rows), cols(cols) {
        elements = new double[rows * cols];
        for (int i = 0; i < cols * rows; i++) elements[i] = v[i];
    }

    ~Matrix() {
        delete[] elements;
    }
};

void MatMul1(Matrix& A, Matrix& B, Matrix& ret) {
    for (int i = 0; i < A.rows; i++) {
        for (int j = 0; j < B.cols; j++) {
            double t = 0;
            for (int k = 0; k < A.cols; k++)
                t += A.elements[i * A.cols, k] * B.elements[k * B.cols + j];
            ret.elements[i * ret.cols + j] = t;
        }
    }
}
void MatMul2(Matrix& A, Matrix& B, Matrix& ret, int t_num, double v) {
    omp_set_num_threads(t_num);
    #pragma omp parallel default(shared)
    {
        int id = omp_get_thread_num();
        for (int i = 0; i < A.rows; i++) {
            if (i % t_num != id) continue;
            for (int j = 0; j < B.cols; j++) {
                double t = 0;
                for (int k = 0; k < A.cols; k++)
                    t += A.elements[i * A.cols, k] * B.elements[k * B.cols + j];
                ret.elements[i * ret.cols + j] = t;
            }
        }
    }
    int f = 1;
    for (int i = 0; i < ret.cols * ret.rows; i++) {
        if (abs(ret.elements[i] - v) > 1e-2) {
            cout << i / ret.cols  << " " << i % ret.cols << " " <<  v << " " << ret.elements[i] << endl;
        }
    }
    if (!f) cout << "error!\n";
}

double getrd() {
    return 10.0 * rand() / RAND_MAX;
}
void test1(int t_num) {
    Matrix A(4, 4, getrd()), B(4, 4, getrd()), C(4, 4, 0.0);
    int per = 10000000 / t_num;
    omp_set_num_threads(t_num);
    int t, i;
    #pragma omp parallel for
    for (t = 0; t < t_num; t++) {
        for (i = 0; i < per; i++) {
            MatMul1(A, B, C);
        }
    }
}
void test2(int t_num) {
    Matrix A(2000, 2000, getrd()), B(2000, 2000, getrd()), C(2000, 2000, 0.0);
    MatMul2(A, B, C, t_num, A.cols * A.elements[0] * B.elements[0]);
}
int main() {
    for (int i = 1; i <= 128; i *= 2) {
        TimeCalculate();
        cout << "Thread num: " << i << endl;
        test1(i);
        TimeCalculate();
    }

    for (int i = 1; i <= 128; i *= 2) {
        TimeCalculate();
        cout << "Thread num: " << i << endl;
        test2(i);
        TimeCalculate();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章