2017-01-13 7 views
1

激しい計算にOpenMPスレッドを使用する予定です。しかし、私は最初の試行で期待されるパフォーマンスを得ることができませんでした。私はそれにいくつかの問題があると思ったが、私はまだ保証していない。一般的に、私はパフォーマンスのボトルネックがフォークと結合モデルから発生していると考えています。あなたはいくつかの点で私を助けてくれますか? 最初に、コンシューマスレッド上で実行されるルートサイクルでは、2つの独立したループといくつかの追加機能があります。openMpを使用した複数の独立したforループのパフォーマンスの問題

void routineFunction(short* xs, float* xf, float* yf, float* h) 
{  
    // Casting 
    #pragma omp parallel for 
    for (int n = 0; n<1024*1024; n++) 
    { 
     xf[n] = (float)xs[n]; 
    } 

    memset(yf,0,1024*1024*sizeof(float)); 
    // Filtering 
    #pragma omp parallel for 
    for (int n = 0; n<1024*1024-1024; n++) 
    { 
     for(int nn = 0; nn<1024; nn++) 
     { 
      yf[n]+=xf[n+nn]*h[nn]; 
     } 
    } 
    status = DftiComputeBackward(hand, yf, yf); // Compute backward transform 
} 

注:機能は既に以下に見られるループ、ルーチンサイクルの終了時との間に配置されるI詳細をクリアするなど、それがより読めるなかったので、このコードは、compiliedすることができません。

OpenMPスレッド番号は動的に8に設定されます。私は、Windowsタスクバーの使用スレッドを観察しました。スレッド数は大幅に増えますが、パフォーマンスの向上は見られませんでした。私はいくつかの推測をしていますが、今後の実装についてあなたと議論したいと思います。

私の質問は次のとおりです。

  1. フォークと結合モデルはスレッドの作成と中止に対応していますか?それはソフトウェアにとって同じコストですか?

  2. routineFunctionがコンシューマによって呼び出されると、OpenMPスレッドは毎回forkとjoinを実行しますか?

  3. rutineFunctionの実行中に、OpenMPスレッドのスレッドフォークを行い、forループごとに結合しますか?または、コンパイラは2番目のループを既存のスレッドを扱うように助けますか?その場合、forループは2回forkとjoinを行い、コードを再度整列させます。パフォーマンスを節約したり、並列領域(#pragma omp parallel)と#pragma omp for#pragma omp parallel forではなく)を使用して共有する方が、ループを1つのループにまとめる方が合理的です。スレッドIDとスレッド番号を使用して静的スケジューリングを強制します。 the document at page 34によれば、静的スケジューリングは負荷の不均衡を引き起こす可能性があります。実際には、私はCUDAプログラミングのため静的スケジューリングに精通していますが、パフォーマンス上の問題があれば、それを避けたいと思います。また、パラレル領域が完成した後、スマートなOpenMPアルゴリズムがマスタースレッドに参加しないことを指摘するstackoverflowの回答をAlexey Kukanov in last paragraphで読みました。最初のループが完了した後、マスタースレッドへの参加を避けるために、OpenMPのビジー待機およびスリープ属性を利用する方法。

  4. コードでパフォーマンスの問題が発生する別の理由はありますか?

+0

2つの質問:どのように時間を測定しますか?パラレル化された領域は、連続したウォールクロック時間の何パーセントに相当しますか? – Gilles

+0

私はclock()を使用しましたが、その時間分解能は1ミリ秒でしたが、routineFunctionを100000回テストし、その平均値を取得しました。 – Abdullah

+2

'clock()'を使用することがタイミングの並列効率に関して今までにない最も悪い考えである理由を理解するには、[this](http://stackoverflow.com/q/10673732/5239503)を参照してください。 – Gilles

答えて

0

これは主にメモリにバインドされたコードです。その性能とスケーラビリティは、メモリチャネルが単位時間に転送できるデータ量によって制限されます。 xfyfは合計で8 MiBを使用します。これはほとんどのサーバーグレードCPUのL3キャッシュに適合しますが、ほとんどのデスクトップまたはラップトップCPUのL3キャッシュには適合しません。 2つまたは3つのスレッドがすでにメモリ帯域幅を飽和させることができる場合は、スレッドを追加することでパフォーマンスが向上するわけではありません。また、shortfloatのキャスティングは、現代のCPUでは4〜5サイクルという比較的高価な操作です。

フォークと結合モデルはスレッドの作成と中止に対応していますか?それはソフトウェアにとって同じコストですか?

routineFunctionがコンシューマによって呼び出されると、OpenMPスレッドは毎回forkとjoinを実行しますか?

このスレッドプライベート変数は、異なる平行間のそれらの値を保持するOpenMP仕様の要件を満足する最も簡単な方法であるようになし、基本的にMSVC++のものを含む、すべてのOpenMPランタイムは、スレッドプールを使用して、並列領域を実装するには地域。最初のparallel領域だけが新しいスレッドを開始するのに十分なコストがかかります。後続の領域はそれらのスレッドを再利用し、前に実行されたparallel領域のいずれかに多くのスレッドが必要な場合にのみ、追加料金が支払われます。まだオーバーヘッドはありますが、毎回新しいスレッドを開始するオーバーヘッドよりはるかに低いです。

rutineFunctionの実行中に、OpenMPスレッドのスレッドフォークを行い、forループごとに結合しますか?または、コンパイラは2番目のループを既存のスレッドを扱うように助けますか?

はい、あなたのケースでは、2つの別々の平行領域が作成されます。手動で1にマージすることができます

#pragma omp parallel 
{ 
    #pragma omp for 
    for (int n = 0; n<1024*1024; n++) 
    { 
     xf[n] = (float)xs[n]; 
    } 

    #pragma omp single 
    { 
     memset(yf,0,1024*1024*sizeof(float)); 
     // 
     // Other code that was between the two parallel regions 
     // 
    } 

    // Filtering 
    #pragma omp for 
    for (int n = 0; n<1024*1024-1024; n++) 
    { 
     for(int nn = 0; nn<1024; nn++) 
     { 
      yf[n]+=xf[n+nn]*h[nn]; 
     } 
    } 
} 

は、コード内のパフォーマンスの問題のためのもう一つの理由はありますか?

メモリにバインドされているか、ここに示す2つ以上のループがあります。

+0

routineFunctionは2回目のforループで苦しみました。なぜなら計算量が多いからです。私は第2の内部ループがL1キャッシュに合うと思った。なぜそれはまだメモリにバインドされていますか?ちなみに、私はXeon E5-4650ベースのプロセッサを使用しました。コアは8コア、スレッドは16スレッドです。そのL1 8x32 KB(8ウェイ)、L3 20 MB(20ウェイ)。 – Abdullah

1

さてさて、私はOpenMPのものはそううまくいけば、私はこれのいずれかのアップ混乱しませんでした...しかし、ここになかったので、それがしばらくしています。

  • フォークとジョインは、スレッドの作成と破棄と同じことです。コストを他のスレッド(C++ 11スレッドなど)と比較する方法は、実装に依存します。一般的なOpenMPスレッドは、C++ 11スレッドよりも軽いかもしれませんが、100%確実ではないと思います。いくつかのテストをする必要があります。

  • 現在たびroutineFunctionはあなたは、ループの最初のためにフォーク参加、第二のループのためのmemset、フォークを行い、参加して、あなたが作成したほうが良いでしょうDftiComputeBackward

  • を呼び出しますと呼ばれていますあなたが述べたようになぜスケジューリングが余分な関心事であるのか分かりません。 memsetを関数の先頭に移動し、指定されたコマンドを使用して並列領域を開始し、前述のように各forループに#pragma omp forが付いていることを確認するのは簡単です。明示的に#pragma omp barrierを2つのforループの間に入れて、すべてのスレッドが2番目のものを開始する前に最初のforループを終了するようにする必要があるかもしれません... OpenMPに暗黙的な障壁がありますが、#pragma omp forに1つまたは

  • コンパイラでOpenMPコンパイルフラグがオンになっていることを確認します。そうでない場合、プラグマは無視され、コンパイルされ、何も変わりません。

  • あなたの操作は、SIMDアクセラレーションのためのものです。コンパイラーが自動ベクトル化をサポートしているかどうか、そしてそれがそれを実行しているかどうかを確認したいかもしれません。そうでなければ、おそらく組み込み関数を使用してSIMDを少し調べます。

  • DftiComputeBackwardsはこのコードに対してどのくらい時間がかかりますか?

+0

はい。私はすでにSIMD命令、AVX 256を使用しています。私は速度を非常に直線的に増やしましたが、まだ十分ではありません。より速いプログラムを必要とするため、マルチスレッドも使用する必要があります。 DftiComputeBacwardsは、合計期間の30%を消費します。 – Abdullah

関連する問題