2012-10-18 5 views
5

データを生成する大きなループがあります。各反復は、例えば1秒を要し、データの塊を生成する。私はすべてのチャンクが正しい順序でファイルに書き込まれる必要があります。私はループを並列化したい場合はループ内のOpenMP同期

、私は(非常に簡略化)このような何かを書くことができ:これはファイルに私の出力を取得しますが、エントリの順序は保証されません

FILE* f = fopen("output.txt", "w"); 
    omp_lock_t lock; 
    omp_init_lock(&lock); 
    int nIterations = 1000000; 
#pragma omp parallel for 
    for(int thread=0; thread<4; thread++) 
    { 
     int a=0, b=0, c=0; 
     for(int n=thread; n<nIterations; n+=4) 
     { 
      int value = do_computations(&a, &b, &c); 
      omp_set_lock(&lock); 
      fprintf(f, "%d\n", value); 
      omp_unset_lock(&lock); 
     } 
    } 
#pragma omp barrier 
    fclose(f); 
    omp_destroy_lock(&lock); 

実行を同期して、すべてのスレッドが自分のタスクを実行してから、マスタスレッドがファイルに書き込みを行った後、スレッドを再開します。いくつかの不可解な理由のために、これは、OpenMP仕様で禁止されて、除き

#pragma omp parallel for 
     for(int thread=0; thread<4; thread++) 
     { 
      int a=0, b=0, c=0; 
      int values[4]; 
      for(int n=thread; n<nIterations; n+=4) 
      { 
       values[n] = do_computations(&a, &b, &c); 
#pragma omp barrier 
       if(thread == 0) 
       { 
         for(int i=0; i<4; i++) 
         fprintf(f, "%d\n", values[i]); 
       } 
#pragma omp barrier 
      } 
     } 
#pragma omp barrier 

:つまり、私はこのような何かをしたいと思います。

または私は

#pragma omp parallel for 
     for(int thread=0; thread<4; thread++) 
     { 
      int a=0, b=0, c=0; 
      for(int n=thread; n<nIterations; n+=4) 
      { 
       int value = do_computations(&a, &b, &c); 
#pragma omp ordered 
       { 
        fprintf(f, "%d\n", value); 
       } 
      } 
     } 
    #pragma omp barrier 
     fclose(f); 

を試みることができる。しかしそのためには、いずれかの動作しません「と、ループの反復が構築物について...つ以上の注文指示を実行してはいけません。」

コードを1つのループとして書き直したくないので、ループを交換したくありません。

他のスレッド/同期ツールを使用せずにOpenMPでこれを行うには、きれいな方法がありますか?

+0

コードを実行しているアーキテクチャ/オペレーティングシステムとは何ですか? – Raj

+0

'parallel for'の代わりに' #pragma omp parallel'を使うことができますか? – Raj

+0

'do_computations'は本当に3つの' 0'を渡していますか?私は 'do_computations'は純粋な関数ではない(つまり副作用がある)と仮定します。もしそうなら、 'do_computations'の副作用は何ですか? 'do_computations'への2回の呼び出しが並行して実行されるとどうなりますか?私は、あなたがそれらを並行して実行することによっても逃げることができることを非常に疑う(副作用があるという仮定に基づいて、したがって実行が発生する順序は問題*)。 - あるいはあなたはコードを単純化していますか?実際のループをよりよく表現できるものを共有するべきでしょうか? – ArjunShankar

答えて

3

計算とIOという2つのことをしようとしています。計算は並列化することができますが、IOは必ずシリアルにする必要があります。しかし、IOを計算と同じループに置くことによって、計算にもシリアル化が強制されますが、これは意味をなさないのです。

すべての計算を行う方がはるかに良いでしょう。次に IOを実行します。 fprintfsのループではなくバイナリで1つの大きなチャンクでデータを書き出すことができれば、これはシリアルであってもほぼ確実に高速になります。

FILE* f = fopen("output.txt", "w"); 
    const int nIterations = 1000000; 
    int values[nIterations]; 

#pragma omp parallel for 
    for(int n=0; n<niterations; n++) 
    { 
     int a=0, b=0, c=0; 
     values[n] = do_computations(&a, &b, &c); 
    } 

    for (int n=0; n<niterations; n++) 
     fprintf(f,"%d\n", values[n]); 

    fclose(f); 

これは、もちろん、より多くのメモリを必要としますが、速度とメモリは一般的なトレードオフです。そのトレードオフの両極端が動作しない場合は、いつでも調整可能なサイズのチャンクで計算を行うことができます。

const int nIterations = 1000000; 
    const int chunkSize = 10000; 
    int values[chunkSize]; 
    int chunkNum = 0; 
    int chunkLeft = chunkSize; 

    for (int start = 0; start < nIterations; start+= chunkSize) { 

     if (start+chunkSize > nIterations) chunkLeft = nIterations - start; 

    #pragma omp parallel for 
     for(int n=start; n<start+chunkLeft; n++) 
     { 
      int a=0, b=0, c=0; 
      values[n-start] = do_computations(&a, &b, &c); 
     } 

     for (int n=0; n<chunkLeft; n++) 
      fprintf(f,"%d\n", values[n]); 

    } 
    fclose(f); 
+0

私たちは、計算とI/Oを重ね合わせる必要がある状況について話しています。確かに、それは他のものをすることは通常簡単です。電源グリッチがあり、すべての結果がループに完了するのを待っているRAMにバッファされていたので、12時間分の計算量が拭き取られるまで、 – user434507

+0

これは素晴らしいですが、コミュニケーションと計算が重複していないので、複雑なプロファイリングでもわかるように、計算をシリアライズしているだけです。 2つを重複させたい場合は、別々のIOタスクを作成し、プロデューサ/コンシューマアプローチを使用して出力をバッファします。 –

+0

私は計算をシリアライズしていません.I/Oには0.01%の時間がかかり、残りのスレッドは並列に実行されます。あなたはこれを思っています。私が元の質問に書いたのは、まさに私が必要なものです。 – user434507

0

私はかつての答えに既に存在していない解決策を提案しようとするでしょう:

#include <stdio.h> 
#include <assert.h> 
#include <unistd.h> 

#define NITER 100 

int main() { 

    FILE * f = fopen("output.bin", "w+"); 

#pragma omp parallel 
    { 
#pragma omp for schedule(runtime) 
    for (int ii = 0; ii < NITER; ++ii) {  
     sleep(1); // Simulate computation 
     printf("%d\n",ii); // Just to be convinced that the loop is not evaluated in serial order 
#pragma omp critical (FILEWRITE) 
     { 
    fseek (f ,sizeof(ii)*ii,SEEK_SET); 
    fwrite(&ii,sizeof(ii),1,f); 
     }  
    } 
    } 

    // Check serially that the file is written in the right order 
    fseek(f,0,SEEK_SET); 
    int value = -1; 
    for (int ii = 0; ii < NITER; ++ii) {   
    fread (&value,sizeof(ii),1,f);  
    assert(value == ii); 
    } 

    fclose(f); 
    return 0; 
} 

このケースは、各チャンクが非常によく定義されたサイズを持つ場合にのみ適用されるため、計算の繰り返しを知っていれば、ファイルの先頭からオフセットを派生させることができます。

つまり、提供しているコードスニペットには、OpenMPの基本を確認する必要があることを示唆する多くのエラーがあります。例えば:誰かがここで答えを探してつまずく場合

#pragma omp parallel for 
    for(int thread=0; thread<4; thread++) 
    { // No need to unroll the loop as OpenMP runtime 
     // map iterations on threads based on the scheduling policy 
     int a=0, b=0, c=0; 
     for(int n=thread; n<nIterations; n+=4) 
     { 
      int value = do_computations(&a, &b, &c); 
      // No need to use lock, when a critical construct suffices 
      omp_set_lock(&lock); 
      fprintf(f, "%d\n", value); 
      omp_unset_lock(&lock); 
     } 
    } // Implicit barrier at the end of the parallel for 
#pragma omp barrier 
// Why a barrier when there is only one thread? 
+0

ロックより重要な利点はありますか? Criticalは少し短いコードを与えますが、どちらのオプションも同じ方法で動作するはずです。 – user434507

+0

@ user434507大きな違いは、ワークシェアリングの構造が「順序付けられた」(私の経験に基づいて、大きな減速を生み出すかもしれない)と宣言されるのを避けるためにfseekを使うことです。それ以外の '#pragma omp critical 'を使うと、' omp.h'の不要な依存関係を避けることができます。バイナリのシリアルバージョンをコンパイルしたい場合は、ソースを '#ifdef _OPENMP'で埋める必要はありません。 – Massimiliano

+0

はい、興味深いアプローチですが、あいにくチャンクサイズがわからないと助かりません。 Windowsでは、omp.hをインクルードする必要があります。そうしないと、リンカエラーが発生します。 – user434507

0

後期パーティーに、しかし、何が必要#pragma omp singleであるが、同様にジョナサンは、dursi @との議論を参照してください。

関連する問題