2016-06-01 12 views
1

私が書いているopenmpコードの予期せぬ(私にとって!)動作を観察しました。コードの構造は以下の通り:私はこのコードの3つのバージョンにコンパイルしているOMP_NUM_THREADS = 1の#pragma omp atomicのパフォーマンスの問題

#pragma omp parallel for 
for(int i=0;i<N;i++){ 
// lots of calculations that produce 3 integers i1,i2,i3 and 3 doubles d1,d2,d3 
#pragma omp atomic 
J1[i1] += d1; 
#pragma omp atomic 
J2[i2] += d2; 
#pragma omp atomic 
J3[i3] += d3; 
} 

:OpenMPのなしのOpenMP(-fopenmp)

2)で

1)

3) openmpを使用していますが、3つの原子操作はありません(原子操作が必要なのでテストと同じです)

環境変数O MP_NUM_THREADS = 1、バージョン2に関して大幅な減速が見られる)。バージョン3はバージョン2と同じ速さで実行されます)。

私はこの動作の理由を知りたいと思います(なぜ、アトミック操作でコードがシングルスレッド化されても遅くなるのですか?)、バージョン1のようなコードをコンパイル/バージョン2と同じ速さで実行されます)。

質問の最後に上記の動作を示す動作例を添付します。

g++ -fopenmp -o toy_code toy_code.cpp -std=c++11 -O3 

2):私は1)コンパイル

g++ -o toy_code_NO_OMP toy_code.cpp -std=c++11 -O3 

及び3)と:

g++ -fopenmp -o toy_code_NO_ATOMIC toy_code_NO_ATOMIC.cpp -std=c++11 -O3 

コンパイラのバージョンは、GCCバージョン5.3.1 20160519(Debianのあります5.3.1-20)。 3つのバージョンの実行時間である。

1)1分24秒

2)51秒

3)51秒任意のアドバイスを事前に

ありがとうございます!

// toy_code.cpp 
#include <stdio.h> 
#include <iostream> 
#include <stdlib.h> 
#include <cmath> 
#include <omp.h> 
#define Np 1000000 
#define N 1000 

int main(){ 
     double* Xp, *Yp, *J,*Jb; 
     Xp = new double[Np]; 
     Yp = new double[Np]; 
     J = new double [N*N]; 
     Jb = new double [N*N]; 

     for(int i=0;i<N*N;i++){ 
      J[i]=0.0; 
      Jb[i]=0.0; 
     } 

     for(int i=0;i<Np;i++){ 
      Xp[i] = rand()*1.0/RAND_MAX - 0.5; 
      Yp[i] = rand()*1.0/RAND_MAX - 0.5; 
     } 

     for(int n=0; n<2000; n++){ 
     #pragma omp parallel for 
     for(int p=0;p<Np;p++){ 
      double rx = (Xp[p]+0.5)*(N-1); 
      double ry = (Yp[p]+0.5)*(N-1); 
      int xindex = (int)floor(rx+0.5); 
      int yindex = (int)floor(ry+0.5); 
      int k; 
      k=xindex*N+yindex; 

      #pragma omp atomic 
      J[k]+=1; 
      #pragma omp atomic 
      Jb[k]+=1; 
     } 
     } 

     delete[] Xp; 
     delete[] Yp; 
     delete[] J; 
     delete[] Jb; 

return 0; 
} 
+0

openmpが有効な場合、プラグマはGOMPコードに展開され、おそらくシーケンシャルコードよりもオーバーヘッドが発生します –

答えて

0

OpenMPを有効にすると、gccは実行時にしか認識されない任意の数のスレッドで動作する異なるコードを生成する必要があります。

この特定のケースでは、出力がgcc -S(lablesによってわずかに短縮されています)を見てください。 OpenMPのなし

.loc 1 38 0 discriminator 2 # Line 38 is J[k]+=1; 
movsd 8(%rsp), %xmm1 
cvttsd2si %xmm0, %edx 
cvttsd2si %xmm1, %eax 
movsd .LC3(%rip), %xmm0 
imull $1000, %eax, %eax 
addl %edx, %eax 
cltq 
salq $3, %rax 
leaq 0(%r13,%rax), %rdx 
.loc 1 40 0 discriminator 2 # Line 40 is Jb[k]+=1; 
addq %r12, %rax 
.loc 1 29 0 discriminator 2 
cmpq $8000000, %r15 
.loc 1 38 0 discriminator 2 
addsd (%rdx), %xmm0 
movsd %xmm0, (%rdx) 
.loc 1 40 0 discriminator 2 
movsd .LC3(%rip), %xmm0 
addsd (%rax), %xmm0 
movsd %xmm0, (%rax) 

ループが、これはかなり複雑作っ繰り出されます。 -fopenmp

movsd (%rsp), %xmm2 
cvttsd2si %xmm0, %eax 
cvttsd2si %xmm2, %ecx 
imull $1000, %ecx, %ecx 
addl %eax, %ecx 
movslq %ecx, %rcx 
salq $3, %rcx 
movq %rcx, %rsi 
addq 16(%rbp), %rsi 
movq (%rsi), %rdx 
movsd 8(%rsp), %xmm1 
jmp .L4 
movq %rax, %rdx 
movq %rdx, (%rsp) 
movq %rdx, %rax 
movsd (%rsp), %xmm3 
addsd %xmm1, %xmm3 
movq %xmm3, %rdi 
lock cmpxchgq %rdi, (%rsi) 
cmpq %rax, %rdx 
jne .L9 
.loc 1 40 0 
addq 24(%rbp), %rcx 
movq (%rcx), %rdx 
jmp .L5 
.p2align 4,,10 
.p2align 3 
movq %rax, %rdx 
movq %rdx, (%rsp) 
movq %rdx, %rax 
movsd (%rsp), %xmm4 
addsd %xmm1, %xmm4 
movq %xmm4, %rsi 
lock cmpxchgq %rsi, (%rcx) 
cmpq %rax, %rdx 
jne .L10 
addq $8, %r12 
cmpq %r12, %rbx 
jne .L6 

私はここで何が起こっているかのすべての詳細を説明したり、理解しようとするつもりはないが、メッセージには必要ではないthatsのよ:コンパイラは異なる原子命令を使用する必要があります特に、lock cmpxchgqが高価になる可能性があります。

この基本的な問題以外にも、OpenMPはオプティマイザを想像できる方法で混乱させる可能性があります。アンローリングに干渉する。また、intelコンパイラが実際にOpenMPループ用のより効率的なシリアルコードを生成するという興味深いケースも見てきました。

P.S.自分が幸運であると考えてください。それはもっと悪くなる可能性があります。コンパイラがアトミック命令をハードウェア命令にマップできない場合は、さらに遅くなるロックを使用する必要があります。

+0

問題を回避するために、スレッドの数を確認するif文を追加しました。どのプラグマ指令もなし。ありがとうございました。 – sparappaband

関連する問題