OpenMp簡介

呂銓銓
13 min readNov 13, 2020

--

前言

記憶體架構分成兩種共享式記憶體與分散式記憶體

共享式記憶體:記憶體是由多個CPU來共同存取的,所以通常CPU和記憶體會放在同一台電腦裡,優點是可以很快存取記憶體,缺點是擴充性不佳。

共享式記憶體

分散式記憶體:利用網路串連多台機器,優點是可以CPU就可以很快存取到記憶體,缺點是機器間的資料交換不易。

分散式記憶體

今天要介紹的OpenMp屬於利用共享式記憶體來對程式平行化處理,而MPI是利用分散式記憶體來對程式平行化處理。

環境架構

  • CPU : Intel i5–6300HQ
  • Main Memory : 8G
  • Disk:128G SSD + 1T HDD
  • OS : CentOS 8

What is OpenMp?

OpenMp(Open Multi-Processing)是一種利用thread進行平行化處理,進而加快程式處理的速度的函式庫,可跨平台使用。
程式語言:C,C++,Fortran

OpenMp架構

OpenMp會在進入parallel region將master thread複製好幾份放到記憶體內同時執行(從parallel region開始的地方執行),最後離開parallel region的時候會等待所有thread執行完畢後再繼續執行master thread的程式

OpenMp教學

首先設定預設的thread數量,當程式碼中沒有指定thread數量時則會使用預設或是以Logical CPU當作預設值,這邊先預設thread數量為2

$  export OMP_NUM_THREADS=2

OpenMp使用語法 : #pragma omp <directive> [clause[[,] clause] …]

OpenMp基本Function

  • omp_get_thread_num() 取得目前thread的id
  • omp_set_num_threads(n) 在程式中設定thread的數量
  • omp_get _num_threads()取得使用中thread的數量
  • omp_set_schedule()設定schedule的方法

Example1:利用parallel進行程式的平行化

#include<omp.h>
#include<stdio.h>
int main(int argc,char* argv[]){
#pragma omp parallel
{
printf("Thread %d Hello World \n",omp_get_thread_num());
}
}

編譯與執行:-fopenmp 用來載入libgomp這個動態函式庫

$   gcc -fopenmp example.c 
$ ./a.out

Output:因為設定兩個thread所以出現0和1,thread 0 為master thread

Example2:for迴圈平行化

#include<omp.h>
#include<stdio.h>
int main(){#pragma omp parallel
{
#pragma omp for
for(int i=0;i<5;i++)
{
printf("thread %d : loop %d\n",omp_get_thread_num(),i);
}
}
return 0;
}

Output:因為是平行化處理所以跑出來的結果不一定照順序

Example3:sections平行化每個section個別平行運算

#include<omp.h>
#include<stdio.h>
#include<stdlib.h>
void Test(int);
int main()
{
#pragma omp parallel sections
{
#pragma omp section
{
for(int i=0;i<100000;++i)
{}
printf("thread %d , first section\n",omp_get_thread_num());
}
#pragma omp section
{
printf("thread %d , second section\n",omp_get_thread_num());
}
#pragma omp section
{
printf("thread %d , third section\n",omp_get_thread_num());
}
#pragma omp section
{
printf("thread %d , fourth section\n",omp_get_thread_num());
}
}
}

Output:因為第1個section跑比較久所以最後才顯示出來

Example4: single 只跑一次,master 只讓master thread跑

#include<omp.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
#pragma omp parallel
{
for(int i=0;i<3;i++)
{
for(int j=0;j<100000;j++){}
printf("thread %d : %d\n",omp_get_thread_num(),i);
}
printf("thread %d : two times\n",omp_get_thread_num());
#pragma omp single
{
printf("thread %d : one times\n",omp_get_thread_num());
}
#pragma omp master
{
printf("thread %d : master \n",omp_get_thread_num());
}
}
}

Output:被single包含的程式只執行一次,被master包含的程式只會讓master執行

Example5:被private包含的變數再跑平行運算時,每個thread會自己複製一份不會共用同一份變數

#include<omp.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
int i,j;
#pragma omp parallel for
for(i=0;i<5;i++)
for(j=0;j<5;j++)
{
printf("thread %d : %d loop\n",omp_get_thread_num(),i*5+j);
}
printf("--------------------------------------------------------\n");
#pragma omp parallel for private(j)
for(i=0;i<5;i++)
for(j=0;j<5;j++)
{
printf("thread %d : %d loop\n",omp_get_thread_num(),i*5+j);
}
}

Output:左邊因為共用變數j而導致迴圈沒跑滿25圈,右邊因為把j複製多份所以沒這問題

Example6:firstprivate 和private差不多只是在複製時也會複製初始值,lastprivate則是會在最後將複製出來的值丟回到本尊

#include <omp.h>
#include <stdio.h>
struct counter {
int count;
};
typedef struct counter counter;int main()
{
counter A;
A.count =-5;
#pragma omp parallel for firstprivate(A) lastprivate(A)
for(int i=0;i<5;i++)
{
A.count++;
printf("Thread %d : count %d\n",omp_get_thread_num(),A.count);
}
printf("final count %d\n",A.count);}

Output:因為firstprivate的關係所以count會從-5往上加,lastprivate則會把最後一條跑完的thread的count賦予到原本的A.count所以才是-3

Example7: atomic是為了保證變數在做計算時不被其他thread跟改到而導致計算出的東西有錯誤(race condition)

#include<omp.h>
#include<stdio.h>
int main()
{
int sum =0;
#pragma omp parallel for
for(int i=0;i<1000;i++)
for(int j=0;j<5000;j++)
#pragma omp atomic
sum+=1;
printf("%d\n",sum);}

Output:如果沒有加atomic跑出來的數字會是低於5,000,000,加了atomic可以保證變數做運算時不會被其他thread給更改到數字

左方為沒加atomic,右方為有加

Example8:reduction目的和上面很像,他是將每個sum依照thread各別複製一份出來後最後join時將所有sum相加就不會導致錯誤發生,但是只可以接受+、*、-、&...等運算符號

#include<omp.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
int sum = 0;
double start = omp_get_wtime();
#pragma omp parallel for reduction(+:sum)
for(int i=0;i<1000;i++)
{
for(int j=0;j<5000;j++)
{
sum+=1;
}
}printf("sum %d : time %4g second\n",sum,omp_get_wtime()-start);
}

Output:如果沒有加reduction出來的數字會因為race condition而有錯誤

上方沒有加reduction,下方有加reduction

Example9:schedule分成4種static,dynamic,guided,runtime,auto

static:將迴圈每n個分一組,依照thread順序輪流給每個thread執行,當跑過一輪後再從第一個thread開始輪流跑

schedule(static, 4):   
**** **** **** ****
**** **** **** ****
**** **** **** ****
**** **** **** ****

dynamic:將迴圈每n個分一組,隨機分配給thread執行

schedule(dynamic, 4):  
**** **** ****
**** **** **** **** ****
**** **** **** **** ****
**** **** ****

guilded:剛開始會依照thread數量下去切,如果迴圈有64個,thread有4個,那一開始第一組的數量則是64/4=16,依序往後每組數量會遞減,收縮到n個一組,如剩下的數量不夠n個則剩下的全部變成1組

schedule(guided, 4):   
*******
************ **** ****
*********
**************** ***** **** ***

runtime:先不指定方法等到要執行時會依照系統變數OMP_SCHEDULE或omp_set_schedule做設定

schedule(runtime): 

auto:由系統幫忙處理

schedule(auto):

schedule(static,4)範例

#include<omp.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main()
{
#pragma omp parallel for schedule(static,4) num_threads(2) ordered
for(int i=0;i<16;i++)
{
#pragma omp ordered
printf("Thread %d has completed iteration %d \n",omp_get_thread_num(),i);
}
printf("All done!\n");return 0;}

Output:將迴圈每4個一組下去跑,每次跑的thread都會照順序,thread0先跑在換thread1,依此類推

Reference

--

--

呂銓銓
呂銓銓

No responses yet