日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

HPC学习(二)

發(fā)布時間:2023/12/14 编程问答 48 豆豆
生活随笔 收集整理的這篇文章主要介紹了 HPC学习(二) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章目錄

  • OMP學習
    • introduction
    • 環(huán)境配置
    • OpenMP API
      • 編譯器指令
      • 庫函數(shù)
      • 互斥鎖
    • 數(shù)據(jù)依賴與沖突
    • 原子操作與鎖
      • 原子操作理解
      • 原子操作與鎖的區(qū)別
    • OpenMP實操(多線程矩陣乘法)

OMP學習

motivation:剛接觸高性能運算,學習方向有點亂,偶然在網(wǎng)上找到一篇博客: ASC18華農(nóng)隊長超算競賽完整備戰(zhàn)指南.,決定按照這個思路進行整理學習。本篇博客主要用于本人學習記錄,如有錯誤,歡迎各位大佬指出。(大佬勿噴…)
這篇繼續(xù)按照指南順序,學習openmp編程。

學omp推薦b站網(wǎng)課:新竹清華大學并行計算與并行編程課程,挑選著看,感覺不需要太多基礎都可以看懂。

introduction

OpenMP = open specification for multi-processing
OpenMP是由一組計算機硬件和軟件供應商聯(lián)合定義的應用程序接口(API);
OpenMP為基于共享內(nèi)存的并行程序的開發(fā)人員提供了一種便攜式和可擴展的編程模型,其API支持各種架構上的C/C++和Fortran;
omp的并行模型稱為fork-join模型
由主線程創(chuàng)造出多個線程(fork過程),
并行代碼執(zhí)行完后,只剩下一個主線程(join過程)

環(huán)境配置

  • 在Windows下很簡單,vs本身就支持omp,只需要在項目屬性頁上左側(cè)選擇“配置屬性”——“C/C++”——“語言”,然后在右側(cè)“OpenMP支持”后填入"Yes",然后在文件中include進<omp.h>即可。
  • 在linux下,一般gcc跟icc都.支持openmp,只要在編譯的時候加上參數(shù)即可
  • gcc -fopenmp source.c -o source

    OpenMP API

    關于OpenMp的API介紹,有一位博主已經(jīng)寫得很全面了,想要深入學習的話去看看。以下內(nèi)容參考了下方博客并根據(jù)我自己的需要簡化,會相對簡略。(博客鏈接:OpenMP學習筆記)

    OpenMP API總覽
    編譯器指令(44個)
    運行時庫函數(shù)(35個)
    環(huán)境變量(13條個)

    編譯器指令

    編譯器指令在你的源代碼中可能被顯示為注釋,并且被編譯器所忽略,除非你明顯地告訴編譯器
    OpenMP的編譯器指令的目標主要有:

  • 產(chǎn)生一個并行區(qū)域
  • 劃分線程中的代碼塊
  • 在線程之間分配循環(huán)迭代
  • 序列化代碼段
  • 同步線程間的工作
  • 指令作用
    parallel用在一個結構塊之前,表示這段代碼將被多個線程并行執(zhí)行
    for用于for循環(huán)語句之前,表示將循環(huán)計算任務分配到多個線程中并行執(zhí)行,以實現(xiàn)任務分擔。注意要保證每次循環(huán)之間無數(shù)據(jù)相關性。
    parallel forparallel和for指令的結合,也是用在for循環(huán)語句之前,表示for循環(huán)體的代碼將被多個線程并行執(zhí)行,它同時具有并行域的產(chǎn)生和任務分擔兩個功能
    sections用在可被并行執(zhí)行的代碼段之前,用于實現(xiàn)多個結構塊語句的任務分擔,可并行執(zhí)行的代碼段各自用section指令標出(注意區(qū)分sections和section
    parallel sectionsparallel和sections兩個語句的結合,類似于parallel for
    single用在并行域內(nèi),表示一段只被單個線程執(zhí)行的代碼
    private子句可以將變量聲明為線程私有,每個線程都有一個該變量的副本,不會互相影響。原變量在并行部分不起任何作用,也不會受到并行部分內(nèi)部操作的影響。
    firstprivate在private的基礎上,用原變量的值初始化線程中的副本
    lastprivate在并行部分結束時,將程序語法上最后一次迭代的值賦回給變量
    threadprivate只針對全局變量,使得每個線程都有一個私有的全局對象
    sharedshared可以將一個變量聲明為共享變量,在多個線程中共享,但要注意使用安全。
    reductionreduction子句可以對一個或者多個參數(shù)指定一個操作符,然后每一個線程都會創(chuàng)建這個參數(shù)的私有拷貝,在并行區(qū)域結束后,迭代運行指定的運算符,并更新原參數(shù)的值。eg:reduction(+: sum)
    copyincopyin子句可以將主線程中變量的值拷貝到各個線程的私有變量中,讓各個線程可以訪問主線程中的變量。copyin的參數(shù)必須要被聲明稱threadprivate.
    schedule(method=static, size=1)線程分配方式,method參數(shù)有static(平均分配,線程間迭代數(shù)最多相差size),dynamic(根據(jù)程序執(zhí)行動態(tài)分配),size表示每次分配的迭代樹最少是多少。

    庫函數(shù)

    接下來介紹部分常用的庫函數(shù)

    庫函數(shù)作用
    int omp_get_num_procs(void)返回調(diào)用函數(shù)時可用的處理器數(shù)目
    int omp_get_num_threads(void)返回當前并行區(qū)域中的活動線程個數(shù)
    int omp_get_thread_num(void)返回當前線程號
    int omp_set_num_threads(void)設置進入并行區(qū)域時,將要創(chuàng)建的線程個數(shù)
    int omp_get_max_threads()返回最大線程數(shù)量,可用上一個函數(shù)修改
    int omp_in_parallel()可以判斷當前是否處于并行狀態(tài),返回0或1
    void omp_set_dynamic(int)該函數(shù)可以設置是否允許在運行時動態(tài)調(diào)整并行區(qū)域的線程數(shù)。0表示禁用,其他數(shù)字表示可用。
    int omp_get_dynamic()返回當前程序是否允許在運行時動態(tài)調(diào)整并行區(qū)域的線程數(shù)。

    互斥鎖

    與互斥鎖相關的函數(shù)我獨立開一個表格來介紹

    庫函數(shù)作用
    void omp_init_lock(omp_lock)初始化互斥鎖
    void omp_destroy_lock(omp_lock)銷毀互斥鎖
    void omp_set_lock(omp_lock)獲得互斥鎖
    void omp_unset_lock(omp_lock)釋放互斥鎖
    bool omp_test_lock(omp_lock)該函數(shù)可以看作是omp_set_lock的非阻塞版本。

    補充一下omp_lock變量。

    定義: omp_lock_t lock; 使用: omp_init_lock(&lock)

    也就是說,omp_lock是一個omp_lock_t類型變量的指針

    數(shù)據(jù)依賴與沖突

    關于這方面的內(nèi)容國內(nèi)網(wǎng)站上好像比較少也比較散亂,我就按自己的理解說一下(水平有限,不敢保證正確性…)
    什么叫數(shù)據(jù)依賴(carries dependency)?舉個栗子就能理解:
    c = a + b;
    e = c + d;
    c 數(shù)據(jù)依賴于 e,因為它需要 c 的值。
    (應該沒理解錯吧…)

    數(shù)據(jù)沖突
    在多線程編程中,當各個線程對某一個變量同時進行讀取修改的時候,線程讀取的順序就會對結果造成影響,這時候就會發(fā)生數(shù)據(jù)沖突。(個人淺見)

    原子操作與鎖

    原子操作理解

    首先要理解什么是原子操作。下面的解釋是從原子操作這篇博客中截取的。

    原子操作(atomic operation)指的是由多步操作組成的一個操作。如果該操作不能原子地執(zhí)行,則要么執(zhí)行完所有步驟,要么一步也不執(zhí)行,不可能只執(zhí)行所有步驟的一個子集。
    現(xiàn)代操作系統(tǒng)中,一般都提供了原子操作來實現(xiàn)一些同步操作,所謂原子操作,也就是一個獨立而不可分割的操作。在單核環(huán)境中,一般的意義下原子操作中線程不會被切換,線程切換要么在原子操作之前,要么在原子操作完成之后。更廣泛的意義下原子操作是指一系列必須整體完成的操作步驟,如果任何一步操作沒有完成,那么所有完成的步驟都必須回滾,這樣就可以保證要么所有操作步驟都未完成,要么所有操作步驟都被完成。
    例如在單核系統(tǒng)里,單個的機器指令可以看成是原子操作(如果有編譯器優(yōu)化、亂序執(zhí)行等情況除外);在多核系統(tǒng)中,單個的機器指令就不是原子操作,因為多核系統(tǒng)里是多指令流并行運行的,一個核在執(zhí)行一個指令時,其他核同時執(zhí)行的指令有可能操作同一塊內(nèi)存區(qū)域,從而出現(xiàn)數(shù)據(jù)競爭現(xiàn)象。多核系統(tǒng)中的原子操作通常使用內(nèi)存柵障(memory barrier)來實現(xiàn),即一個CPU核在執(zhí)行原子操作時,其他CPU核必須停止對內(nèi)存操作或者不對指定的內(nèi)存進行操作,這樣才能避免數(shù)據(jù)競爭問題。

    說實話,在閱讀完上述解釋后,我還是有些云里霧里的,我再找了一個簡單的程序,也許能幫助理解。

    #include<iostream> #include<omp.h> #include<time.h> #include<Windows.h> using namespace std;int main(int argc, char** argv) {omp_set_num_threads(6);int counter = 0, i; #pragma omp parallel{for (i = 0; i < 10; i++){ #pragma omp atomiccounter++;}}cout << "counter =" << counter;return 0; }

    一共6個線程,因此理論輸出為6*10=60,符合實際結果。

    原子操作與鎖的區(qū)別

  • 互斥鎖是一種數(shù)據(jù)結構,用來讓一個線程執(zhí)行程序的關鍵部分,完成互斥的多個操作。
  • 原子操作是針對某個值的單個互斥操作。
  • 可以把互斥鎖理解為悲觀鎖,共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉(zhuǎn)讓給其它線程。
  • 一般而言,使用原子操作能得到更好的性能。

    OpenMP實操(多線程矩陣乘法)

    //矩陣乘法 #include<iostream> #include<omp.h> #include<time.h> #include<Windows.h> using namespace std;//int main(int argc, char** argv) //{ // omp_set_num_threads(6); // int counter = 0, i; //#pragma omp parallel // { // for (i = 0; i < 10; i++) // { //#pragma omp atomic // counter++; // } // } // cout << "counter =" << counter; // return 0; // // //}#define Max 10 #define Min -10 #define Size 500//隨機數(shù)生成 double getrandom(int max = Max, int min = Min) {double tmp1 = double(rand() % 101) / 101;double tmp2 = (double)((rand() % (max - min)) + min);return tmp1+tmp2; }//生成指定大小的隨機方陣 void getmatrix(double* Matrix,int size = Size) {for (int i = 0; i < size; i++)for (int j = 0; j < size; j++)Matrix[i * size + j] = getrandom(); }//微秒級別的windows平臺計時函數(shù) ////傳統(tǒng)方法 double* baseline(double* A, double* B, int size = Size) {double* result;result = new double[size * size];for(int i = 0; i < size; i++)for (int j = 0; j < size; j++) {result[i * size + j] = 0;for (int k = 0; k < size; k++)result[i * size + j] += A[i * size + k] * B[k * size + j];}return result; }//多線程版本1 double* Multithread(double* A, double* B, int size = Size) {double* result;result = new double[size * size];int i, j, k;#pragma omp parallel for schedule(dynamic) firstprivate(i,j,k)for (i = 0; i < size; i++) //#pragma omp forfor (j = 0; j < size; j++) {result[i * size + j] = 0; //#pragma omp parallel for num_threads(3)for (k = 0; k < size; k++)result[i * size + j] += A[i * size + k] * B[k * size + j];}return result; }int main(int argc, char** argv) {double* A, * B, * C;int n = Size;srand((unsigned)time(NULL));//分配動態(tài)空間A = new double[n * n];B = new double[n * n];C = new double[n * n];getmatrix(A);getmatrix(B);//初始化結束,準備開始計時LARGE_INTEGER large_interger; //記錄頻率和周期的結構體double dff; //頻率__int64 c1, c2; //周期QueryPerformanceFrequency(&large_interger);dff = large_interger.QuadPart;QueryPerformanceCounter(&large_interger);c1 = large_interger.QuadPart;C = baseline(A, B);QueryPerformanceCounter(&large_interger);c2 = large_interger.QuadPart;cout << "baseline運行時間為(單位:毫秒):" << (c2 - c1) * 1000 / dff << '\n';cout << C[50, 50] << '\n';QueryPerformanceFrequency(&large_interger);dff = large_interger.QuadPart;QueryPerformanceCounter(&large_interger);c1 = large_interger.QuadPart;C = Multithread(A, B);QueryPerformanceCounter(&large_interger);c2 = large_interger.QuadPart;cout << "多線程矩陣乘法1運行時間為(單位:毫秒):" << (c2 - c1) * 1000 / dff << '\n';cout << C[50, 50] << '\n';delete [] A;delete [] B;delete [] C;return 0; }

    代碼部分會有幾個警告,還不太清楚要如何處理,但似乎對結果沒有什么影響。
    事實上,通過修改循環(huán)順序以及使用矩陣分塊能夠進一步提升代碼性能,但我是懶得寫了,,,有興趣的可以參考“程序性能優(yōu)化探討(6)——矩陣乘法優(yōu)化之分塊矩陣”,這篇寫的非常詳細。


    openmp暫時就寫那么多,以后有想法會繼續(xù)補充。
    下一篇應該會開始寫MPI。

    總結

    以上是生活随笔為你收集整理的HPC学习(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。