欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

OpenMP深入剖析reduction子句教程

 更新時間:2022年11月11日 15:04:55   作者:一無是處的研究僧  
這篇文章主要為大家介紹了OpenMP深入剖析reduction子句教程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

在前面的教程OpenMP入門當中我們簡要介紹了 OpenMP 的一些基礎(chǔ)的使用方法,在本篇文章當中我們將從一些基礎(chǔ)的問題開始,然后仔細介紹在 OpenMP 當中 reduction 子句的各種使用方法。

從并發(fā)求和開始

我們的任務(wù)是兩個線程同時對一個變量 data 進行 ++操作,執(zhí)行 10000 次,我們看下面的代碼有什么問題:

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
static int data;
int main() {
  #pragma omp parallel num_threads(2) // 使用兩個線程同時執(zhí)行上面的代碼塊
  {
    for(int i = 0; i < 10000; i++) {
      data++;
      usleep(10);
    }
    // omp_get_thread_num 函數(shù)返回線程的 id 號 這個數(shù)據(jù)從 0 開始,0, 1, 2, 3, 4, ...
    printf("data = %d tid = %d\n", data, omp_get_thread_num());
  }
  printf("In main function data = %d\n", data);
  return 0;
}

在上面的代碼當中,我們開啟了兩個線程并且同時執(zhí)行 $pragma 下面的代碼塊,但是上面的程序有一個問題,就是兩個線程可能同時執(zhí)行 data++ 操作,但是同時執(zhí)行這個操作的話,就存在并發(fā)程序的數(shù)據(jù)競爭問題,在 OpenMP 當中默認的數(shù)據(jù)使用方式就是??‍♂?線程之間是共享的比如下面的執(zhí)行過程:

  • 首先線程 1 和線程 2 將 data 加載到 CPU 緩存當中,當前的兩個線程得到的 data 的值都是 0 。
  • 線程 1 和線程 2 對 data 進行 ++ 操作,現(xiàn)在兩個線程的 data 的值都是 1。
  • 線程 1 將 data 的值寫回到主存當中,那么主存當中的數(shù)據(jù)的值就等于 1 。
  • 線程 2 將 data 的值寫回到主存當中,那么主存當中的數(shù)據(jù)的值也等于 1 。

但是上面的執(zhí)行過程是存在問題的,因為我們期望的是主存當中的 data 的值等于 2,因此上面的代碼是存在錯誤的。

解決求和問題的各種辦法

使用數(shù)組巧妙解決并發(fā)程序當中的數(shù)據(jù)競爭問題

在上面的程序當中我們使用了一個函數(shù) omp_get_thread_num 這個函數(shù)可以返回線程的 id 號,我們可以根據(jù)這個 id 做一些文章,如下面的程序:

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
static int data;
static int tarr[2];
int main() {
  #pragma omp parallel num_threads(2)
  {
    int tid = omp_get_thread_num();
    for(int i = 0; i < 10000; i++) {
      tarr[tid]++;
      usleep(10);
    }
    printf("tarr[%d] = %d tid = %d\n", tid, tarr[tid], tid);
  }
  data = tarr[0] + tarr[1];
  printf("In main function data = %d\n", data);
  return 0;
}

在上面的程序當中我們額外的使用了一個數(shù)組 tarr 用于保存線程的本地的和,然后在最后在主線程里面講線程本地得到的和相加起來,這樣的話我們得到的結(jié)果就是正確的了。

$./lockfree01.out
tarr[1] = 10000 tid = 1
tarr[0] = 10000 tid = 0
In main function data = 20000

在上面的程序當中我們需要知道的是,只有當并行域當中所有的線程都執(zhí)行完成之后,主線程才會繼續(xù)執(zhí)行并行域后面的代碼,因此主線程在執(zhí)行代碼

  data = tarr[0] + tarr[1];
  printf("In main function data = %d\n", data);

之前,OpenMP 中并行域中的代碼全部執(zhí)行完成,因此上面的代碼執(zhí)行的時候數(shù)組 tarr 中的結(jié)果已經(jīng)計算出來了,因此上面的代碼最終的執(zhí)行結(jié)果是 2000。

reduction 子句

在上文當中我們使用數(shù)組去避免多個線程同時操作同一個數(shù)據(jù)的情況,除了上面的方法處理求和問題,我們還有很多其他方法去解決這個問題,下面我們使用 reduction 子句去解決這個問題:

#include <stdio.h>
#include <omp.h>
#include <unistd.h>
static int data;
int main() {
  #pragma omp parallel num_threads(2) reduction(+:data)
  {
    for(int i = 0; i < 10000; i++) {
      data++;
      usleep(10);
    }
    printf("data = %d tid = %d\n", data, omp_get_thread_num());
  }
  printf("In main function data = %d\n", data);
  return 0;
}

在上面的程序當中我們使用了一個子句 reduction(+:data) 在每個線程里面對變量 data 進行拷貝,然后在線程當中使用這個拷貝的變量,這樣的話就不存在數(shù)據(jù)競爭了,因為每個線程使用的 data 是不一樣的,在 reduction 當中還有一個加號?,這個加號表示如何進行規(guī)約操作,所謂規(guī)約操作簡單說來就是多個數(shù)據(jù)逐步進行操作最終得到一個不能夠在進行規(guī)約的數(shù)據(jù)。

例如在上面的程序當中我們的規(guī)約操作是 + ,因此需要將線程 1 和線程 2 的數(shù)據(jù)進行 + 操作,即線程 1 的 data 加上 線程 2 的 data 值,然后將得到的結(jié)果賦值給全局變量 data,這樣的話我們最終得到的結(jié)果就是正確的。

如果有 4 個線程的話,那么就有 4 個線程本地的 data(每個線程一個 data)。那么規(guī)約(reduction)操作的結(jié)果等于:

(((data1 + data2) + data3) + data4) 其中 datai 表示第 i 個線程的得到的 data 。

除了后面的兩種方法解決多個線程同時對一個數(shù)據(jù)進行操作的問題的之外我們還有一些其他的辦法去解決這個問題,我們在下一篇文章當中進行仔細分析。

深入剖析 reduction 子句

我們在寫多線程程序的時候可能會存在這種需求,每個線程都會得到一個數(shù)據(jù)的結(jié)果,然后在最后需要將每個線程得到的數(shù)據(jù)進行求和,相乘,或者邏輯操作等等,在這種情況下我們可以使用 reduction 子句進行操作。redcution 子句的語法格式如下:

reduction(操作符:變量)

當我們使用 reduction 子句的時候線程使用的是與外部變量同名的變量,那么這個同名的變量的初始值應(yīng)該設(shè)置成什么呢?具體的設(shè)置規(guī)則如下所示:

運算符初始值
+/加法0
*/乘法1
&&/邏輯與1
||/邏輯或0
min/最小值對應(yīng)類型的最大值
max/最大值對應(yīng)類型的最小值
&/按位與所有位都是 1
|/按位或所有位都是 0
^/按位異或所有位都是 0

下面我們使用各種不同的例子去分析上面的所有的條目:

加法+操作符

我們使用下面的程序去測試使用加法規(guī)約的正確性,并且在并行域當中打印進行并行域之前變量的值。

#include <stdio.h>
#include <omp.h>
static int data;
int main() {
  #pragma omp parallel num_threads(2) reduction(+:data)
  {
    printf("初始值 : data = %d tid = %d\n", data, omp_get_thread_num());
    if(omp_get_thread_num() == 0) {
      data = 10;
    }else if(omp_get_thread_num() == 1){
      data = 20;
    }
    printf("變化后的值 : data = %d tid = %d\n", data, omp_get_thread_num());
  }
  printf("規(guī)約之后的值 : data = %d\n", data);
  return 0;
}

上面的程序的輸出結(jié)果如下所示:

初始值 : data = 0 tid = 0
變化后的值 : data = 10 tid = 0
初始值 : data = 0 tid = 1
變化后的值 : data = 20 tid = 1
規(guī)約之后的值 : data = 30

從上面的輸出結(jié)果我們可以知道當進入并行域之后我們的變量的初始值等于 0 ,第一個線程的線程 id 號等于 0 ,它將 data 的值賦值成 10 ,第二個線程的線程 id 號 等于 1,它將 data 的值賦值成 20 。在出并行域之前會將兩個線程得到的 data 值進行規(guī)約操作,在上面的代碼當中也就是+操作,并且將這個值賦值給全局變量 data 。

乘法*操作符

#include <stdio.h>
#include <omp.h>
static int data = 2;
int main() {
  #pragma omp parallel num_threads(2) reduction(*:data)
  {
    printf("初始值 : data = %d tid = %d\n", data, omp_get_thread_num());
    if(omp_get_thread_num() == 0) {
      data = 10;
    }else if(omp_get_thread_num() == 1){
      data = 20;
    }
    printf("變化后的值 : data = %d tid = %d\n", data, omp_get_thread_num());
  }
  printf("規(guī)約之后的值 : data = %d\n", data);
  return 0;
}

上面的程序輸出結(jié)果如下所示:

初始值 : data = 1 tid = 0
變化后的值 : data = 10 tid = 0
初始值 : data = 1 tid = 1
變化后的值 : data = 20 tid = 1
規(guī)約之后的值 : data = 400

從上面的程序的輸出結(jié)果來看,當我們使用*操作符的時候,我們可以看到程序當中 data 的初始值確實被初始化成了 1 ,而且最終在主函數(shù)當中的輸出結(jié)果也是符合預(yù)期的,因為 400 = 2 * 10 * 20,其中 2 只在全局變量初始化的時候的值。

邏輯與&&操作符

#include <stdio.h>
#include <omp.h>
static int data = 100;
int main() {
  #pragma omp parallel num_threads(2) reduction(&&:data)
  {
    printf("data =\t %d tid = %d\n", data, omp_get_thread_num());
    if(omp_get_thread_num() == 0) {
      data = 10;
    }else if(omp_get_thread_num() == 1){
      data = 20;
    }
  }
  printf("data = %d\n", data);
  return 0;
}

上面的程序的輸出結(jié)果如下所示:

初始化值 : data = 1 tid = 0
初始化值 : data = 1 tid = 1
在主函數(shù)當中 : data = 1

從上面的輸出結(jié)果我們可以知道,程序當中數(shù)據(jù)的初始化的值是沒有問題的,你可能會疑惑為什么主函數(shù)當中的 data 值等于 1,這其實就是 C 語言當中對 && 操作服的定義,如果最終的結(jié)果為真,那么值就等于 1,即 100 && 10 && 20 == 1,你可以寫一個程序去驗證這一點。

或||操作符

#include <stdio.h>
#include <omp.h>
static int data = 100;
int main() {
  #pragma omp parallel num_threads(2) reduction(||:data)
  {
    printf("初始化值 : data = %d tid = %d\n", data, omp_get_thread_num());
    if(omp_get_thread_num() == 0) {
      data = 0;
    }else if(omp_get_thread_num() == 1){
      data = 0;
    }
  }
  printf("在主函數(shù)當中 : data = %d\n", data);
  return 0;
}

上面的程序輸出結(jié)果如下所示:

初始化值 : data = 1 tid = 0
初始化值 : data = 1 tid = 1
在主函數(shù)當中 : data = 1

從上面的結(jié)果看出,數(shù)據(jù)初始化的值是正確的,主函數(shù)當中得到的數(shù)據(jù)也是正確的,因為 100 || 0 || 0 == 1,這個也是 C 語言的條件或得到的結(jié)果。

MIN 最小值

#include <stdio.h>
#include <omp.h>
static int data = 1000;
int main() {
  printf("Int 類型的最大值等于 %d\n", __INT32_MAX__);
  #pragma omp parallel num_threads(2) reduction(min:data)
  {
    printf("data =\t\t     %d tid = %d\n", data, omp_get_thread_num());
    if(omp_get_thread_num() == 0) {
      data = 10;
    }else if(omp_get_thread_num() == 1){
      data = 20;
    }
  }
  printf("data = %d\n", data);
  return 0;
}

上面的程序執(zhí)行結(jié)果如下所示:

Int 類型的最大值等于   2147483647
data =               2147483647 tid = 0
data =               2147483647 tid = 1
data = 10

 

可以看出來初始化的值是正確的,當我們求最小值的時候,數(shù)據(jù)被正確的初始化成對應(yīng)數(shù)據(jù)的最大值了,然后我們需要去比較這幾個值的最小值,即 min(1000, 0, 20) == 10 ,因此在主函數(shù)當中的到的值等于 10。

MAX 最大值

#include <stdio.h>
#include <omp.h>
static int data = 1000;
int main() {
  #pragma omp parallel num_threads(2) reduction(max:data)
  {
    printf("data = %d tid = %d\n", data, omp_get_thread_num());
    if(omp_get_thread_num() == 0) {
      data = 10;
    }else if(omp_get_thread_num() == 1){
      data = 20;
    }
  }
  printf("data = %d\n", data);
  return 0;
}

上面的程序輸出結(jié)果如下所示:

data = -2147483648 tid = 0
data = -2147483648 tid = 1
data = 1000

可以看出程序被正確的初始化成最小值了,主函數(shù)當中輸出的數(shù)據(jù)應(yīng)該等于 max(1000, 10, 20) 因此也滿足條件。

& 按位與

#include <stdio.h>
#include <omp.h>
static int data = 15;
int main() {
  #pragma omp parallel num_threads(2) reduction(&:data)
  {
    printf("data = %d tid = %d\n", data, omp_get_thread_num());
    if(omp_get_thread_num() == 0) {
      data = 8;
    }else if(omp_get_thread_num() == 1){
      data = 12;
    }
  }
  printf("data = %d\n", data);
  return 0;
}

上面的程序輸出結(jié)果如下:

data = -1 tid = 0
data = -1 tid = 1
data = 8

首先我們需要知道上面幾個數(shù)據(jù)的比特位表示:

-1 = 1111_1111_1111_1111_1111_1111_1111_1111
8  = 0000_0000_0000_0000_0000_0000_0000_1000
12 = 0000_0000_0000_0000_0000_0000_0000_1100
15 = 0000_0000_0000_0000_0000_0000_0000_1111

我們知道當我們使用 & 操作符的時候初始值是比特為全部等于 1 的數(shù)據(jù),也就是 -1,最終進行按位與操作的數(shù)據(jù)為 15、8、12,即在主函數(shù)當中輸出的結(jié)果等于 (8 & 12 & 15) == 8,因為只有第四個比特位全部為 1,因此最終的結(jié)果等于 8 。

|按位或

#include <stdio.h>
#include <omp.h>
static int data = 1;
int main() {
  #pragma omp parallel num_threads(2) reduction(|:data)
  {
    printf("data = %d tid = %d\n", data, omp_get_thread_num());
    if(omp_get_thread_num() == 0) {
      data = 8;
    }else if(omp_get_thread_num() == 1){
      data = 12;
    }
  }
  printf("data = %d\n", data);
  return 0;
}

上面的程序輸出結(jié)果如下所示:

data = 0 tid = 0
data = 0 tid = 1
data = 13

我們還是需要了解一下上面的數(shù)據(jù)的比特位表示:

0  = 0000_0000_0000_0000_0000_0000_0000_0000
1  = 0000_0000_0000_0000_0000_0000_0000_0001
8  = 0000_0000_0000_0000_0000_0000_0000_1000
12 = 0000_0000_0000_0000_0000_0000_0000_1100
13 = 0000_0000_0000_0000_0000_0000_0000_1101

線程初始化的數(shù)據(jù)等于 0 ,這個和前面談到的所有的比特位都設(shè)置成 0 是一致的,我們對上面的數(shù)據(jù)進行或操作之后得到的結(jié)果和對應(yīng)的按位或得到的結(jié)果是相符的。

^按位異或

#include <stdio.h>
#include <omp.h>
static int data = 1;
int main() {
  #pragma omp parallel num_threads(2) reduction(^:data)
  {
    printf("data = %d tid = %d\n", data, omp_get_thread_num());
    if(omp_get_thread_num() == 0) {
      data = 8;
    }else if(omp_get_thread_num() == 1){
      data = 12;
    }
  }
  printf("data = %d\n", data);
  return 0;
}

上面的程序的輸出結(jié)果如下所示:

data = 0 tid = 0
data = 0 tid = 1
data = 5

各個數(shù)據(jù)的比特位表示:

0  = 0000_0000_0000_0000_0000_0000_0000_0000
1  = 0000_0000_0000_0000_0000_0000_0000_0001
8  = 0000_0000_0000_0000_0000_0000_0000_1000
12 = 0000_0000_0000_0000_0000_0000_0000_1100
5  = 0000_0000_0000_0000_0000_0000_0000_0101

大家可以自己對照的進行異或操作,得到的結(jié)果是正確的。

總結(jié)

在本篇文章當中我們主要使用一個例子介紹了如何解決并發(fā)程序當中的競爭問題,然后也使用了 reduction 子句去解決這個問題,隨后介紹了在 OpenMP 當中 reduction 各種規(guī)約符號的使用!

在本篇文章當中主要給大家介紹了 OpenMP 的基本使用和程序執(zhí)行的基本原理,在后續(xù)的文章當中我們將仔細介紹各種 OpenMP 的子句和指令的使用方法,希望大家有所收獲!

以上就是OpenMP深入剖析reduction子句教程的詳細內(nèi)容,更多關(guān)于OpenMP剖析reduction子句的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C語言實現(xiàn)電子時鐘程序

    C語言實現(xiàn)電子時鐘程序

    這篇文章主要為大家詳細介紹了C語言實現(xiàn)電子時鐘程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-11-11
  • 詳解C++中shared_ptr的使用教程

    詳解C++中shared_ptr的使用教程

    shared_ptr能夠記錄對象被引用的次數(shù),主要被用來管理動態(tài)創(chuàng)建的對象的銷毀,這里我們就來詳解C++中shared_ptr的使用教程,需要的朋友可以參考下
    2016-05-05
  • 一文詳解如何在VS?Code上搭建C/C++開發(fā)環(huán)境

    一文詳解如何在VS?Code上搭建C/C++開發(fā)環(huán)境

    VSCode是由微軟開發(fā)的一款免費、開源、跨平臺的文本編輯器,它具有許多強大的功能,這篇文章主要給大家介紹了關(guān)于如何在VS?Code上搭建C/C++開發(fā)環(huán)境的相關(guān)資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2024-03-03
  • C++ 情懷游戲貪吃蛇的實現(xiàn)流程詳解

    C++ 情懷游戲貪吃蛇的實現(xiàn)流程詳解

    貪吃蛇是曾經(jīng)諾基亞手機上很經(jīng)典很經(jīng)典的傳統(tǒng)老游戲,80.90后的童年時不多的游戲之一,跟電腦上的掃雷一樣,下面來從貪吃蛇手機游戲的前世今生講起
    2021-11-11
  • VS Code 中安裝運行、編寫C語言程序的詳細教程

    VS Code 中安裝運行、編寫C語言程序的詳細教程

    這篇文章主要介紹了VS Code 中安裝運行、編寫C語言程序的詳細教程,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-03-03
  • C++11新特性之右值引用與完美轉(zhuǎn)發(fā)詳解

    C++11新特性之右值引用與完美轉(zhuǎn)發(fā)詳解

    C++11標準為C++引入右值引用語法的同時,還解決了一個短板,即使用簡單的方式即可在函數(shù)模板中實現(xiàn)參數(shù)的完美轉(zhuǎn)發(fā)。本文就來講講二者的應(yīng)用,需要的可以參考一下
    2022-09-09
  • C語言編寫掃雷小程序

    C語言編寫掃雷小程序

    這篇文章主要為大家詳細介紹了C語言編寫掃雷小程序,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • C++中的throw關(guān)鍵字詳解

    C++中的throw關(guān)鍵字詳解

    throw關(guān)鍵字是在C語言中用來拋出異常的關(guān)鍵字,它通常與try和catch一起使用,用于在程序中發(fā)生錯誤時進行異常處理,當遇到無法處理的錯誤情況時,我們可以使用throw關(guān)鍵字主動拋出異常,所以本文給大家詳細的介紹一下C++中的throw關(guān)鍵字,需要的朋友可以參考下
    2023-09-09
  • C++中浮點類型的具體使用

    C++中浮點類型的具體使用

    C++提供了不同精度的浮點類型,主要有?float、double?和?long?double,這些浮點類型具有不同的字節(jié)大小和范圍,用于滿足不同應(yīng)用場景的精度要求,本文主要介紹了C++中浮點類型的具體使用,感興趣的可以了解一下
    2023-08-08
  • Qt QStandardItemModel用法小結(jié)

    Qt QStandardItemModel用法小結(jié)

    QStandardItemModel可用作標準Qt數(shù)據(jù)類型的存儲庫,本文主要介紹了Qt QStandardItemModel用法小結(jié),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-12-12

最新評論