簡體   English   中英

減少 OpenMP 中的數組

[英]Reducing on array in OpenMP

我正在嘗試並行化以下程序,但不知道如何減少數組。 我知道這是不可能的,但有沒有其他選擇? 謝謝。 (我增加了對 m 的減少,這是錯誤的,但想就如何做到這一點提出建議。)

#include <iostream>
#include <stdio.h>
#include <time.h>
#include <omp.h>
using namespace std;

int main ()
{
  int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
  int S [10];

  time_t start_time = time(NULL);
  #pragma omp parallel for private(m) reduction(+:m)
  for (int n=0 ; n<10 ; ++n ){
    for (int m=0; m<=n; ++m){
      S[n] += A[m];
    }
  }
  time_t end_time = time(NULL);
  cout << end_time-start_time;

  return 0;
}

是的,可以使用 OpenMP 進行陣列縮減。 在 Fortran 中,它甚至為此有構造。 在 C/C++ 中,你必須自己做。 這里有兩種方法可以做到。

第一種方法為每個線程制作私有版本的S ,並行填充它們,然后在臨界區將它們合並到S (見下面的代碼)。 第二種方法創建一個維度為 10*nthreads 的數組。 並行填充此數組,然后將其合並到S而不使用臨界區。 第二種方法要復雜得多,如果您不小心,可能會出現緩存問題,尤其是在多路系統上。 有關更多詳細信息,請參閱此Fill histograms (array reduction) in parallel with OpenMP without using a critical section

第一種方法

int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
int S [10] = {0};
#pragma omp parallel
{
    int S_private[10] = {0};
    #pragma omp for
    for (int n=0 ; n<10 ; ++n ) {
        for (int m=0; m<=n; ++m){
            S_private[n] += A[m];
        }
    }
    #pragma omp critical
    {
        for(int n=0; n<10; ++n) {
            S[n] += S_private[n];
        }
    }
}

第二種方法

int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
int S [10] = {0};
int *S_private;
#pragma omp parallel
{
    const int nthreads = omp_get_num_threads();
    const int ithread = omp_get_thread_num();

    #pragma omp single 
    {
        S_private = new int[10*nthreads];
        for(int i=0; i<(10*nthreads); i++) S_private[i] = 0;
    }
    #pragma omp for
    for (int n=0 ; n<10 ; ++n )
    {
        for (int m=0; m<=n; ++m){
            S_private[ithread*10+n] += A[m];
        }
    }
    #pragma omp for
    for(int i=0; i<10; i++) {
        for(int t=0; t<nthreads; t++) {
            S[i] += S_private[10*t + i];
        }
    }
}
delete[] S_private;

我對 Zboson 的回答有兩點看法:
1. 方法 1 當然是正確的,但歸約循環實際上是串行運行的,因為#pragma omp critical這當然是必要的,因為部分矩陣對於每個線程都是局部的,並且相應的歸約必須由線程完成矩陣。
2. 方法2:初始化循環可以移到單節之外,因此變得可並行化。

以下程序使用 openMP v4.0 用戶定義的縮減工具實現數組縮減:

/* Compile with:
     gcc -Wall -fopenmp -o ar ar.c
   Run with:
     OMP_DISPLAY_ENV=TRUE OMP_NUM_THREADS=10 OMP_NESTED=TRUE ./ar
*/
#include <stdio.h>
#include <omp.h>
struct m10x1 {int v[10];};
int A [] =       {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};  
struct m10x1 S = {{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}};
int n,m=0;

void print_m10x1(struct m10x1 x){
  int i;
  for(i=0;i<10;i++) printf("%d ",x.v[i]);
  printf("\n");
}

struct m10x1 add_m10x1(struct m10x1 x,struct m10x1 y){
  struct m10x1 r ={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}};
  int i;
  for (i=0;i<10;i++) r.v[i]=x.v[i]+y.v[i];
  return r;
}

#pragma omp declare reduction(m10x1Add: struct m10x1: \
omp_out=add_m10x1(omp_out, omp_in)) initializer( \
omp_priv={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}} )

int main ()
{
  #pragma omp parallel for reduction(m10x1Add: S)
  for ( n=0 ; n<10 ; ++n )
    {
      for (m=0; m<=n; ++m){
        S.v[n] += A[m];
      }
    }
  print_m10x1(S);
}

這逐字遵循OpenMP 4.0 特性的第 97 頁上的復數減少示例。

雖然並行版本工作正常,但可能存在性能問題,我還沒有調查過:

  1. add_m10x1 輸入和輸出按值傳遞。
  2. add_m10x1 中的循環是串行運行的。

所說的“性能問題”是我自己造成的,不介紹它們是完全直接的:

  1. add_m10x1 的參數應該通過引用傳遞(通過 C 中的指針,C++ 中的引用)
  2. add_m10x1 中的計算應該就地完成。
  3. add_m10x1應聲明為 void 並刪除 return 語句。 結果通過第一個參數返回。
  4. 應相應地修改聲明減少編譯指示,組合器應該只是一個函數調用而不是賦值(v4.0 規范 p181 第 9,10 行)。
  5. add_m10x1 中的 for 循環可以通過 omp parallel for pragma 並行化
  6. 應啟用並行嵌套(例如,通過 OMP_NESTED=TRUE)

修改后的代碼部分是:

void add_m10x1(struct m10x1 * x,struct m10x1 * y){
  int i;
  #pragma omp parallel for
  for (i=0;i<10;i++) x->v[i] += y->v[i];
}

#pragma omp declare reduction(m10x1Add: struct m10x1: \
add_m10x1(&omp_out, &omp_in)) initializer( \
omp_priv={{ 0,  0,  0,  0,  0,  0,  0,  0, 0,  0}} )

由於沒有提到其他答案,我添加了這個答案。

我正在嘗試並行化以下程序,但不知道如何減少數組。 我知道這樣做是不可能的,但是有 > 替代方案嗎?

使用OpenMP 4.5,您可以使用 pragmas 減少數組,即:

#pragma omp parallel for reduction(+:S)

一個完整的運行示例:

#include <iostream>
#include <stdio.h>
#include <time.h>
#include <omp.h>
using namespace std;

int main ()
{
  int A [] = {84, 30, 95, 94, 36, 73, 52, 23, 2, 13};
  int S [10] = {0};

  #pragma omp parallel for reduction(+:S)
  for (int n=0 ; n<10 ; ++n ){
    for (int m=0; m<=n; ++m){
      S[n] += A[m];
    }
  }
  int expected_output [] = {84, 114, 209, 303, 339, 412, 464, 487, 489, 502};
  for(int i = 0; i < 10; i++){
      if(S[i] == expected_output[i])
        printf("%d\n", S[i]);
     else
       printf("ERROR! it should have been %d instead of %d\n", expected_output[i], S[i]);
  }

  return 0;
}

輸出:

84
114
209
303
339
412
464
487
489
502

對於並行循環,每個線程將根據調度程序處理給定的循環索引子集。 然后數組 S 不需要減少,因為每個索引 n 將獨立處理外循環。 也不應該有競爭條件的問題,因為每個線程將寫入不同的位置 S[n]。 所以上面的代碼只使用指令就可以正常工作

#pragma omp parallel for

對於外循環。

如果將您的代碼轉換為 Fortran(它可以在 OpenMP 縮減操作中使用數組)沒有吸引力,您可以使用一堆臨時變量。 例如

int S0, S1, S2, ..., S9;
...
#pragma omp parallel for private(...) shared(S0, S1, S2, ..., S9) \
            reduction(+:S0, S1, S2, ..., S9)
for ...

這讓您不得不編寫某種ifcase語句來確定要更新哪個臨時文件,這種前景並不吸引人。 如果您的代碼只是您想用於學習的示例,請繼續。

但是,如果您的意圖是真正編寫並行前綴和例程,請四處搜索。 這是一個很好的起點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM