並行編程OpenMP基礎及簡單示例

OpenMP基本概念


OpenMP是一種用於共享內存並行系統的多線程程序設計方案,支持的編程語言包括C、C++和Fortran。OpenMP提供了對並行算法的高層抽象描述,特別適合在多核CPU機器上的並行程序設計。編譯器根據程序中添加的pragma指令,自動將程序並行處理,使用OpenMP下降了並行編程的難度和複雜度。當編譯器不支持OpenMP時,程序會退化成普通(串行)程序。程序中已有的OpenMP指令不會影響程序的正常編譯運行。ios


在VS中啓用OpenMP很簡單,不少主流的編譯環境都內置了OpenMP。在項目上右鍵->屬性->配置屬性->C/C++->語言->OpenMP支持,選擇「是」便可。算法


OpenMP執行模式


OpenMP採用fork-join的執行模式。開始的時候只存在一個主線程,當須要進行並行計算的時候,派生出若干個分支線程來執行並行任務。當並行代碼執行完成以後,分支線程會合,並把控制流程交給單獨的主線程。編程

一個典型的fork-join執行模型的示意圖以下:多線程



OpenMP編程模型以線程爲基礎,經過編譯製導指令制導並行化,有三種編程要素能夠實現並行化控制,他們分別是編譯製導、API函數集和環境變量。併發


編譯製導


編譯製導指令以#pragma omp 開始,後邊跟具體的功能指令,格式如:#pragma omp 指令[子句[,子句] …]。經常使用的功能指令以下:編程語言

  • parallel:用在一個結構塊以前,表示這段代碼將被多個線程並行執行;
    for:用於for循環語句以前,表示將循環計算任務分配到多個線程中並行執行,以實現任務分擔,必須由編程人員本身保證每次循環之間無數據相關性;
    parallel for:parallel和for指令的結合,也是用在for循環語句以前,表示for循環體的代碼將被多個線程並行執行,它同時具備並行域的產生和任務分擔兩個功能;
    sections:用在可被並行執行的代碼段以前,用於實現多個結構塊語句的任務分擔,可並行執行的代碼段各自用section指令標出(注意區分sections和section);
    parallel sections
    :parallel和sections兩個語句的結合,相似於parallel for;
    single:用在並行域內,表示一段只被單個線程執行的代碼;
    critical:用在一段代碼臨界區以前,保證每次只有一個OpenMP線程進入;
    flush:保證各個OpenMP線程的數據影像的一致性;
    barrier:用於並行域內代碼的線程同步,線程執行到barrier時要停下等待,直到全部線程都執行到barrier時才繼續往下執行;
    atomic:用於指定一個數據操做須要原子性地完成;
    master:用於指定一段代碼由主線程執行;
    threadprivate:用於指定一個或多個變量是線程專用,後面會解釋線程專有和私有的區別。

相應的OpenMP子句爲: 

  • private
    :指定一個或多個變量在每一個線程中都有它本身的私有副本;
    firstprivate:指定一個或多個變量在每一個線程都有它本身的私有副本,而且私有變量要在進入並行域或任務分擔域時,繼承主線程中的同名變量的值做爲初值;
    lastprivate:是用來指定將線程中的一個或多個私有變量的值在並行處理結束後複製到主線程中的同名變量中,負責拷貝的線程是for或sections任務分擔中的最後一個線程; 
    reduction:用來指定一個或多個變量是私有的,而且在並行處理結束後這些變量要執行指定的歸約運算,並將結果返回給主線程同名變量;
    nowait:指出併發線程能夠忽略其餘制導指令暗含的路障同步;
    num_threads:指定並行域內的線程的數目; 
    schedule:指定for任務分擔中的任務分配調度類型;
    shared:指定一個或多個變量爲多個線程間的共享變量;
    ordered:用來指定for任務分擔域內指定代碼段須要按照串行循環次序執行;
    copyprivate:配合single指令,將指定線程的專有變量廣播到並行域內其餘線程的同名變量中;
    copyin:用來指定一個threadprivate類型的變量須要用主線程同名變量進行初始化;
    default:用來指定並行域內的變量的使用方式,缺省是shared。


API函數


除上述編譯製導指令以外,OpenMP還提供了一組API函數用於控制併發線程的某些行爲,下面是一些經常使用的OpenMP API函數以及說明: 函數


環境變量


OpenMP中定義一些環境變量,能夠經過這些環境變量控制OpenMP程序的行爲,經常使用的環境變量:優化

  • OMP_SCHEDULE:用於for循環並行化後的調度,它的值就是循環調度的類型;  
    OMP_NUM_THREADS
    :用於設置並行域中的線程數;  
    OMP_DYNAMIC
    :經過設定變量值,來肯定是否容許動態設定並行域內的線程數;  
    OMP_NESTED
    :指出是否能夠並行嵌套。 


簡單示例之parallel使用


parallel制導指令用來建立並行域,後邊要跟一個大括號將要並行執行的代碼放在一塊兒:atom

#include<iostream>
#include"omp.h"

using namespace std;

void main()
{
#pragma omp parallel
	{
		cout << "Test" << endl;
	}
	system("pause");
}

執行以上程序有以下輸出:spa


程序打印出了4個「Test」,說明parallel後的語句被4個線程分別執行了一次,4個是程序默認的線程數,還能夠經過子句num_threads顯式控制建立的線程數:

#include<iostream>
#include"omp.h"

using namespace std;

void main()
{
#pragma omp parallel num_threads(6)
	{
		cout << "Test" << endl;
	}
	system("pause");
}

編譯運行有以下輸出:


程序中顯式定義了6個線程,因此parallel後的語句塊分別被執行了6次。第二行的空行是因爲每一個線程都是獨立運行的,在其中一個線程輸出字符「Test」以後尚未來得及換行時,另外一個線程直接輸出了字符「Test」。


簡單示例之parallel for使用


使用parallel制導指令只是產生了並行域,讓多個線程分別執行相同的任務,並無實際的使用價值。parallel for用於生成一個並行域,並將計算任務在多個線程之間分配,從而加快計算運行的速度。可讓系統默認分配線程個數,也可使用num_threads子句指定線程個數。

#include<iostream>
#include"omp.h"

using namespace std;

void main()
{
#pragma omp parallel for num_threads(6)
	for (int i = 0; i < 12; i++)
	{
		printf("OpenMP Test, 線程編號爲: %d\n", omp_get_thread_num());
	}
	system("pause");
}

運行輸出:


上邊程序指定了6個線程,迭代量爲12,從輸出能夠看到每一個線程都分到了12/6=2次的迭代量。


OpenMP效率提高以及不一樣線程數效率對比


#include<iostream>
#include"omp.h"

using namespace std;

void test()
{
	for (int i = 0; i < 80000; i++)
	{
	}
}

void main()
{
	float startTime = omp_get_wtime();

	//指定2個線程
#pragma omp parallel for num_threads(2)
	for (int i = 0; i < 80000; i++)
	{
		test();
	}
	float endTime = omp_get_wtime();
	printf("指定 2 個線程,執行時間: %f\n", endTime - startTime);
	startTime = endTime;

	//指定4個線程
#pragma omp parallel for num_threads(4)
	for (int i = 0; i < 80000; i++)
	{
		test();
	}
	endTime = omp_get_wtime();
	printf("指定 4 個線程,執行時間: %f\n", endTime - startTime);
	startTime = endTime;

	//指定8個線程
#pragma omp parallel for num_threads(8)
	for (int i = 0; i < 80000; i++)
	{
		test();
	}
	endTime = omp_get_wtime();
	printf("指定 8 個線程,執行時間: %f\n", endTime - startTime);
	startTime = endTime;

	//指定12個線程
#pragma omp parallel for num_threads(12)
	for (int i = 0; i < 80000; i++)
	{
		test();
	}
	endTime = omp_get_wtime();
	printf("指定 12 個線程,執行時間: %f\n", endTime - startTime);
	startTime = endTime;

	//不使用OpenMP
	for (int i = 0; i < 80000; i++)
	{
		test();
	}
	endTime = omp_get_wtime();
	printf("不使用OpenMP多線程,執行時間: %f\n", endTime - startTime);
	startTime = endTime;

	system("pause");
}

以上程序分別指定了二、四、八、12個線程和不使用OpenMP優化來執行一段垃圾程序,輸出以下:


可見,使用OpenMP優化後的程序執行時間是原來的1/4左右,而且並非線程數使用越多效率越高,通常線程數達到4~8個的時候,不能簡單經過提升線程數來進一步提升效率。

相關文章
相關標籤/搜索