3

CUDA でコーディングを始めたばかりで、GPU を最大限に活用するために、スレッドの実行方法とメモリへのアクセス方法の概念を理解しようとしています。CUDA のベスト プラクティス ガイド、本 CUDA by Example、およびここにあるいくつかの投稿を読みました。また、Mark Harris によるリダクションの例は非常に興味深く有用であることがわかりましたが、すべての情報にもかかわらず、詳細についてかなり混乱しました。

列単位の操作を行う大きな 2D 配列 (N*M) があるとします。配列をブロックに分割して、各ブロックに 32 の倍数のスレッド数が含まれるようにします (すべてのスレッドが複数のワープに収まります)。各ブロックの最初のスレッドは、追加のメモリ (初期配列のコピーですが、独自の次元のサイズのみ) を割り当て、_shared_ を使用してポインターを共有します同じブロックのすべてのスレッドが同じメモリにアクセスできるように変数。スレッド数は 32 の倍数であるため、1 回の読み取りでアクセスするにはメモリも 32 の倍数である必要があります。ただし、配列の幅が (32*x)+2 列になるように、メモリ ブロックの周囲に余分なパディング、境界線が必要です。境界線は大きな配列を分解することで得られるため、隣接する配列のコピーが一時的に利用できる重複領域があります。

Coleased メモリ アクセス:

ブロックのスレッドがローカル メモリ ブロックにアクセスしていると想像してください。

1  int x = threadIdx.x;
2 
3  for (int y = 0; y < height; y++)
4  {
5    double value_centre = array[y*width + x+1]; // remeber we have the border so we need an offset of + 1 
6    double value_left   = array[y*width + x  ]; // hence the left element is at x
7    double value_right  = array[y*width + x+2]; // and the right element at x+2 
8  
9    // .. do something
10 }

さて、私の理解では、避けられないオフセット (+1,+2) があるため、ワープごとおよび割り当てごとに少なくとも 2 つの読み取り (左側の要素を除く) があるか、それとも問題ではないということです。最初のスレッドの後のメモリが完全に整列している限り、どこから読み始めますか? また、そうでない場合は、配列の幅が (32*x)+2 であり、32 バイトで整列されていないため、最初の行の後に各行の配列へのアクセスが整列されていないことに注意してください。ただし、さらにパディングすると、新しい行ごとに問題が解決します。

質問:上記の例では、オフセットなしでアクセスされるのは最初の行のみであり、配列内の左の要素のみが許可されるという私の理解は正しいですか?

ワープで実行されるスレッド:

ワープ内のスレッドは、すべての命令が同じである場合にのみ並列で実行されます (リンクによる)。条件付きステートメント/分岐実行がある場合、その特定のスレッドは、他のスレッドとのワープ内ではなく、単独で実行されます。

たとえば、配列を初期化すると、次のようなことができます

1 int x = threadIdx.x;
2
3 array[x+1] = globalArray[blockIdx.x * blockDim.x + x]; // remember the border and therefore use +1
4 
5 if (x == 0 || x == blockDim.x-1) // border
6 {
7   array[x] = DBL_MAX;
8 }

ワープのサイズは 32 で、3 行目まで並列に実行され、その後他のすべてのスレッドで停止し、最初と最後のスレッドのみがさらに実行されて境界線を初期化しますか、それとも最初から他のすべてのスレッドから分離されますか?他のすべてのスレッドが満たさない if ステートメントがありますか?

質問:糸はどのようにして単一のたて糸に集められますか? ワープ内の各スレッドは、同じ命令を共有する必要があります。関数全体でこれを有効にする必要がありますか? これは、スレッド 1 (x=0) には当てはまりません。これは、ボーダーも初期化するため、他とは異なるためです。私の理解では、スレッド 1 は 1 つのワープで実行され、スレッド (2-33 など) は別のワープで実行され、ミスアラインメントのために単一の読み取りでメモリにアクセスしません。もう一方の境界線により、単一の縦糸に糸が通されます。あれは正しいですか?

行ごとにどちらかのメモリを完全に整列させるベストプラクティスは何だろうか (その場合、各ブロックを (32*x-2) スレッドで実行して、境界線のある配列が (32*x-2)+ になるようにする) 2 新しい行ごとに 32 の倍数) または、上記で示したように、ブロックごとに 32 の倍数のスレッドを使用して、アライメントされていないメモリをそのまま使用します。この種の質問は必ずしも単純ではなく、特定のケースに依存することが多いことは承知していますが、特定のことは悪い習慣であり、習慣にすべきではない場合があります.

少し試してみたところ、実行時間の違いはあまりわかりませんでしたが、私の例が単純すぎたのかもしれません。ビジュアル プロファイラーから情報を取得しようとしましたが、提供されるすべての情報を十分に理解できていません。しかし、占有レベルが 17% であるという警告が表示されました。これは非常に低いに違いないと思います。したがって、何か問題があると思います。スレッドがどのように並行して実行されるか、およびメモリ アクセスがどれほど効率的であるかについての情報を見つけることができませんでした。

-編集-

2 つの質問を追加して強調表示しました。1 つはメモリ アクセスに関するもので、もう 1 つはスレッドが単一のワープにどのように収集されるかに関するものです。

4

1 に答える 1

2

さて、私の理解では、オフセット (+1,+2) があるため、これは避けられないため、ワープごとおよび割り当てごとに少なくとも 2 つの読み取りが行われます (左側の要素を除く)、またはそれは問題ではありません。最初のスレッドの後のメモリが完全に整列している限り、どこから読み始めますか?

はい、完全な合体を達成しようとしている場合は、「どこから読み始めるか」が重要です。完全な合体とは、特定のワープと特定の命令の読み取りアクティビティがすべて、同じ 128 バイトに整列されたキャッシュラインから来ることを意味します。

質問: 上記の例では、オフセットなしでアクセスされるのは最初の行のみであり、配列内の左の要素のみが許可されるという私の理解は正しいですか?

はい。cc2.0 以降のデバイスの場合、キャッシュによって、アライメントされていないアクセスの欠点の一部が軽減される場合があります。

質問: 糸はどのようにして単一のたて糸に集められますか? ワープ内の各スレッドは、同じ命令を共有する必要があります。関数全体でこれを有効にする必要がありますか? これは、スレッド 1 (x=0) には当てはまりません。これは、ボーダーも初期化するため、他とは異なるためです。私の理解では、スレッド 1 は 1 つのワープで実行され、スレッド (2-33 など) は別のワープで実行され、ミスアラインメントのために単一の読み取りでメモリにアクセスしません。もう一方の境界線により、単一の縦糸に糸が通されます。あれは正しいですか?

ワープへのスレッドのグループ化は常に同じルールに従い、記述したコードの詳細に基づいて変化することはありませんが、起動構成によってのみ影響を受けます。すべてのスレッドが参加するわけではないコード (if ステートメントなど) を記述した場合でも、ワープはロックステップで進行しますが、参加しないスレッドはアイドル状態になります。このように境界線を埋める場合、完全に整列または結合された読み取りを取得することはめったにないため、心配する必要はありません。マシンはその柔軟性を提供します。

于 2013-11-14T13:02:26.140 に答える