2

2 次元配列で熱力学シミュレーションを行っています。配列は 1024x1024 です。while ループは、指定された回数だけ、または goodTempChange が false になるまで反復します。goodTempChange は、定義された EPSILON 値よりも大きいブロックの温度変化に基づいて、true または false に設定されます。配列内のすべてのブロックがその値を下回っている場合、プレートは静止しています。プログラムは動作します。コードに問題はありません。私の問題は、シリアル コードが openmp コードを水から完全に吹き飛ばしていることです。どうしてか分かりません。希望する正方形の周囲の上下左右の 4 つのブロックの平均である平均計算以外はすべて削除しようとしましたが、それでもシリアル コードによって破壊されています。私は以前にopenmpをやったことがなく、自分が持っていることをするためにオンラインでいくつかのものを調べました。可能な限り最も効率的な方法で重要な領域内に変数を配置しました。競合状態はありません。何が悪いのか本当にわかりません。どんな助けでも大歓迎です。ありがとう。

while(iterationCounter < DESIRED_ITERATIONS && goodTempChange) {
  goodTempChange = false;
  if((iterationCounter % 1000 == 0) && (iterationCounter != 0)) {
    cout << "Iteration Count      Highest Change    Center Plate Temperature" << endl;
    cout << "-----------------------------------------------------------" << endl;
    cout << iterationCounter << "               "
         << highestChange << "            " << newTemperature[MID][MID] << endl;
    cout << endl;
  }

  highestChange = 0;

  if(iterationCounter != 0)
    memcpy(oldTemperature, newTemperature, sizeof(oldTemperature));

  for(int i = 1; i < MAX-1; i++) {  
  #pragma omp parallel for schedule(static) 
    for(int j = 1; j < MAX-1; j++) {
      bool tempGoodChange = false;
      double tempHighestChange = 0;
      newTemperature[i][j] = (oldTemperature[i-1][j] + oldTemperature[i+1][j] +
                              oldTemperature[i][j-1] + oldTemperature[i][j+1]) / 4;

      if((iterationCounter + 1) % 1000 == 0) {
        if(abs(oldTemperature[i][j] - newTemperature[i][j]) > highestChange)
          tempHighestChange = abs(oldTemperature[i][j] - newTemperature[i][j]);
        if(tempHighestChange > highestChange) {
          #pragma omp critical
          {
            if(tempHighestChange > highestChange)
              highestChange = tempHighestChange;
          }
        }
      }
      if(abs(oldTemperature[i][j] - newTemperature[i][j]) > EPSILON
         && !tempGoodChange)
        tempGoodChange = true;

      if(tempGoodChange && !goodTempChange) {
        #pragma omp critical
        {
          if(tempGoodChange && !goodTempChane)
            goodTempChange = true;
        }
      }
    }
  }
  iterationCounter++;
}
4

3 に答える 3

1

これらの重要なセクションを取り除こうとすると、役立つ場合があります。例えば:

#pragma omp critical
{
  if(tempHighestChange > highestChange)
  {
    highestChange = tempHighestChange;
  }
}

highestChangeここでは、各スレッドによって計算された をローカル変数に格納し、並列セクションが終了すると、最大の を取得highestChangeできます。

于 2013-10-03T12:35:01.830 に答える
0

これが私の試みです(テストされていません)。

double**newTemperature;
double**oldTemperature;

while(iterationCounter < DESIRED_ITERATIONS && goodTempChange) {
  if((iterationCounter % 1000 == 0) && (iterationCounter != 0))
    std::cout
      << "Iteration Count      Highest Change    Center Plate Temperature\n"
      << "---------------------------------------------------------------\n" 
      << iterationCounter << "               "
      << highestChange << "            "
      << newTemperature[MID][MID] << '\n' << std::endl;

  goodTempChange = false;
  highestChange  = 0;

  // swap pointers to arrays (but not the arrays themselves!)
  std::swap(newTemperature,oldTemperature);
  if(iterationCounter != 0)
    std::swap(newTemperature,oldTemperature);

  bool CheckTempChange = (iterationCounter + 1) % 1000 == 0;
#pragma omp parallel
  {
    bool localGoodChange = false;
    double localHighestChange = 0;
#pragma omp for
    for(int i = 1; i < MAX-1; i++) {
      //
      // note that putting a second
      // #pragma omp for
      // here has (usually) zero effect. this is called nested parallelism and
      // usually not implemented, thus the new nested team of threads has only
      // one thread.
      //
      for(int j = 1; j < MAX-1; j++) {
        newTemperature[i][j] = 0.25 *   // multiply is faster than divide
          (oldTemperature[i-1][j] + oldTemperature[i+1][j] +
           oldTemperature[i][j-1] + oldTemperature[i][j+1]);
        if(CheckTempChange)
          localHighestChange =
            std::max(localHighestChange,
                     std::abs(oldTemperature[i][j] - newTemperature[i][j]));
        localGoodChange = localGoodChange ||
          std::abs(oldTemperature[i][j] - newTemperature[i][j]) > EPSILON;
        // shouldn't this be < EPSILON? in the previous line?
      }
    }
    //
    // note that we have moved the critical sections out of the loops to
    // avoid any potential issues with contentions (on the mutex used to
    // implement the critical section). Also note that I named the sections,
    // allowing simultaneous update of goodTempChange and highestChange
    //
    if(!goodTempChange && localGoodChange)
#pragma omp critical(TempChangeGood)
      goodTempChange = true;
    if(CheckTempChange && localHighestChange > highestChange)
#pragma omp critical(TempChangeHighest)
      highestChange = std::max(highestChange,localHighestChange);
  }
  iterationCounter++;
}

オリジナルにはいくつかの変更があります。

  1. ネストされたループの内側ではなく外側forが並列に実行されます。これは大きな違いを生むはずです。 編集で追加:コメントから、これの重要性を理解していないように見えるので、説明させてください。元のコードでは、外側のループ ( over i) はマスター スレッドによってのみ実行されました。すべての についてi、スレッドのチームが作成され、内側のループjが並列で実行されました。これにより、すべてのi! 代わりに、外側のループを で並列化するiと、このオーバーヘッドは 1 回だけ発生し、各スレッドは のj シェアに対して i内側のループ全体を実行します。したがって、常に可能な限り最も外側のループを並列化するは、マルチスレッド コーディングの基本的な知恵です。

  2. 二重forループは並列領域内にあり、重要な領域呼び出しをループごとのスレッドごとに 1 つに最小限に抑えwhileます。whileループ全体を並列領域内に配置することも検討できます。

  3. を回避するために2つの配列間で交換します(他の回答で提案されているのと同様)memcpyが、これは実際にはパフォーマンスが重要ではありません。 編集で追加: std::swap(newTemperature,oldTemperature) ポインター値のみをスワップし、ポイントされているメモリはスワップしません。もちろん、それがポイントです。

最後に、プディングの証拠は食べることにあることを忘れないで#pragma omp forください。内側または外側のループの前にあることでどのような違いが生じるかを試してみてください。SO について尋ねる前に、常にこのような実験を行ってください。

于 2013-10-03T13:10:39.257 に答える
-1

whileループの開始にかかる時間だけでなく、ループ内のコード全体にかかる時間に関心があると思いますfor(int i = 1; i < MAX-1; i++)

この操作

if(iterationCounter != 0)
{
    memcpy(oldTemperature, newTemperature, sizeof(oldTemperature));
}

これは不要であり、大規模な配列の場合、パフォーマンスを低下させるのに十分な場合があります。old2 つの配列とを維持する代わりに、new2 つの平面を持つ 1 つの 3D 配列を維持します。2 つの整数変数を作成し、それらをoldおよび と呼び、最初newに および に設定します。交換01

newTemperature[i][j] = ((oldTemperature[i-1][j] +  oldTemperature[i+1][j] + oldTemperature[i][j-1] + oldTemperature[i][j+1]) / 4);

temperature[new][i][j] = 
  (temperature[old][i-1][j] +
   temperature[old][i+1][j] +
   temperature[old][i][j-1] +
   temperature[old][i][j+1])/4;

そして、更新の最後にoldandの値を交換しnewて、更新が逆になるようにします。old/newを配列の最初のインデックスにするか、最後のインデックスにするかは、あなたに任せます。このアプローチにより、メモリ内で (大量の) データを移動する必要がなくなります。

深刻な速度低下または加速の失敗の別の考えられる原因は、この SO の質問と回答で説明されています。のサイズの配列を見るたびに、2^nキャッシュの問題が疑われます。

于 2013-10-03T11:59:41.330 に答える