[英]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 頁上的復數減少示例。
雖然並行版本工作正常,但可能存在性能問題,我還沒有調查過:
所說的“性能問題”是我自己造成的,不介紹它們是完全直接的:
修改后的代碼部分是:
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 ...
這讓您不得不編寫某種if
或case
語句來確定要更新哪個臨時文件,這種前景並不吸引人。 如果您的代碼只是您想用於學習的示例,請繼續。
但是,如果您的意圖是真正編寫並行前綴和例程,請四處搜索。 這是一個很好的起點。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.