1

これは C++ AP の問題というよりも GPU の問題であると想定しているため、広くタグ付けします。

作業をいくつかのタイルに分割して作業を行い、その結果をグローバルメモリの既存の値に追加する計算の実装があります。最初に、タイル内のすべてのスレッドが、部分結果を tile_static メモリに計算します。各スレッドには、書き込み先のインデックスがあります。その後、タイルの最初のスレッドがすべてのパーツ結果を合計し、その合計をグローバル メモリ内の位置に追加します。

タイル (タイル内のスレッド 0) は同じ場所に書き込みたい場合があるため、単純なロックを追加しました。

inline void lock(int *lockVariable) restrict(amp)
{
    while (atomic_exchange(lockVariable, 1) != 0);
}

inline void unlock(int *lockVariable) restrict(amp)
{
    *lockVariable = 0;
}

ロックとロック解除に渡すロック変数は、タイルが書き込む競合するメモリ位置ごとに 1 つの整数を持つ整数のグローバル配列にあります。

タイルの最初のスレッドによって行われる、タイル結果の実際の書き込みは、次のように行われます。

//now the FIRST thread in the tile will summ all the pulls into one
if (idx.local[0] == 0)
{                   
  double_4 tileAcceleration = 0;
  for (int i = 0; i < idx.tile_dim0; i++)
  {
    tileAcceleration += threadAccelerations[i];
  }
  lock(&locks[j]);
  //now the FIRST thread in the tile will add this to the global result
  acceleration[j] += tileAcceleration;
  unlock(&locks[j]);
}

これはほとんど問題なく動作しますが、常にではありません。書き込むメモリ位置の数に対してタイルが多すぎる (ロックをめぐる争いが多すぎる) 場合、タイルの結果を適切に追加できないことがあるため、何らかの競合状態が存在する必要があります。

めったにありませんが、ロック/ロック解除のセットアップで正しい追加が保証されない場合があります。

これは、合計の前にロックを上に移動することで「修正」できるため、ロックが取得されてからスレッド0が実際の書き込みを行うまでに時間がかかります。合計に 5 つの要素が残っている場合は、ロックを取得して「修正」することもできます。両方を以下に示します

最初の修正は非常に遅い (ブロックが長すぎる)

if (idx.local[0] == 0)
{                   
  lock(&locks[j]); //get lock right away
  double_4 tileAcceleration = 0;
  for (int i = 0; i < idx.tile_dim0; i++)
  {
    tileAcceleration += threadAccelerations[i];
  }
  //now the FIRST thread in the tile will add this to the global result
  acceleration[j] += tileAcceleration;
  unlock(&locks[j]);
}

2 番目の修正で、少し高速です

if (idx.local[0] == 0)
{                   
  lock(&locks[j]); //this is a "fix" but a slow one
  double_4 tileAcceleration = 0;
  for (int i = 0; i < idx.tile_dim0; i++)
  {
    tileAcceleration += threadAccelerations[i];
    if (i == idx.tile_dim0 - 5) lock(&locks[j]); //lock when almost done
  }
  //now the FIRST thread in the tile will add this to the global result
  acceleration[j] += tileAcceleration;
  unlock(&locks[j]);
}

これらの「修正」がどのように機能するかを見ると、一部のメモリ書き込みがシステム全体で十分な速さで更新されていないことが明らかです。1 つのタイルで場所のロック、書き込み、ロック解除を行うことができます。次に、別のタイルがロックを取得し、追加を行い (ただし、更新されていない古いデータを参照)、ロックを解除します。

ロックは int であり、データは double_4 であるため、ロックはすぐに解放され、データがまだ転送中に他のタイルが確認できるように更新されているようです。別のタイルは、最初のタイルの書き込みがまだ完全にコミットされていなくても、ロックが解放されていると見なすことができます。したがって、2 番目のタイルは、更新されていないデータ値をキャッシュから読み取り、それに追加して書き込みます...

最初のタイルが書き込みを行ったときにデータが (キャッシュ内で) 無効化されなかった理由を正確に理解するのを手伝ってもらえますか? また、誰かがこの問題の適切な解決策を見つけるのを手伝ってくれますか?

4

1 に答える 1