7

うまくいけば処理速度を上げるために、書いたプログラムをCUDAに変換しようとしています。

明らかに、私の古いプログラムは多くの関数を次々に実行します。これらの関数をメイン プログラムで分離し、それぞれを順番に呼び出します。

void main ()
{
  *initialization of variables*
  function1()
  function2()
  function3()
  print result;
}

関数 2 は関数 1 の結果に依存するため、これらの関数は本質的にシリアルです。

さて、これらの関数をカーネルに変換し、関数内のタスクを並列で実行したいと思います。

各関数を並列に書き直してから、メイン プログラムで各カーネルを次々と呼び出すのと同じくらい簡単ですか? これは必要以上に遅いですか?たとえば、次のカーネルを初期化するために CPU に戻ることなく、GPU に次の並列操作を直接実行させることはできますか?

実行中のデータ転送量を制限するために、すべてのランタイム変数を GPU メモリに保持することは明らかですが、カーネル呼び出し間の時間についても心配する必要がありますか?

この質問が明確であることを願っています。そうでない場合は、詳しく教えてください。ありがとう。

そして、私の正気を確認できるように、追加の質問があります。最終的に、このプログラムの入力はビデオ ファイルであり、さまざまな機能を通じて、各フレームが結果につながります。私の計画は、一度に複数のフレーム (たとえば、8 つの一意のフレーム) を取得し、これらの 8 つのフレームの間でブロックの総数を分割することです。その後、ブロック内の複数のスレッドが画像データに対してさらに並列操作を実行します。 、ベクトル加算、フーリエ変換など。
これは問題にアプローチする正しい方法ですか?

4

3 に答える 3

6

プレーンな CPU バージョンからの移植作業をほとんど行わずに、GPU で最大限の速度でプログラムを実行できる場合がいくつかあります。これはその 1 つかもしれません。

このような機能を持つことが可能であれば:

void process_single_video_frame(void* part_of_frame)
{
  // initialize variables
  ...
  intermediate_result_1 = function1(part_of_frame);
  intermediate_result_2 = function2(intermediate_result_1);
  intermediate_result_3 = function3(intermediate_result_2);
  store_results(intermediate_result_3);
}

同時に多くの part_of_frames を処理できます数千と言って、

function1()、ほとんど同じコード パスを通過します (つまり、プログラム フローはフレームの内容に大きく依存しません) function2()function3()

次に、ローカル メモリがすべての作業を行います。ローカル メモリは、グローバル メモリに格納されるメモリの一種です。グローバル メモリとは、微妙ではあるが深遠な点で異なります。メモリは、隣接するスレッドが隣接する 32 ビット ワードにアクセスするように単純にインターリーブされます。ローカル メモリ配列の同じ場所。

プログラムの流れは、ローカル配列にコピーすることから始めpart_of_frame、中間結果のために他のローカル配列を準備することです。次に、コード内のさまざまな関数間でローカル配列へのポインターを渡します。

擬似コード:

const int size_of_one_frame_part = 1000;

__global__ void my_kernel(int* all_parts_of_frames) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    int my_local_array[size_of_one_frame_part];
    memcpy(my_local_array, all_parts_of_frames + i * size_of_one_frame_part);
    int local_intermediate_1[100];
    function1(local_intermediate_1, my_local_array);
    ...
}

__device__ void function1(int* dst, int* src) {
   ...
}

要約すると、このアプローチでは、CPU 関数をほとんど変更せずに使用できる可能性があります。これは、関数の並列化されたバージョンを作成することではなく、関数のチェーン全体を並列に実行することによって並列処理が行われるためです。これも、ローカル配列のメモリをインターリーブするためのハードウェア サポートによって可能になります。

ノート:

  • part_of_frameグローバル メモリからローカル メモリへの最初のコピーは結合されていませんが、それを隠すのに十分な計算があることを願っています。

  • コンピューティング能力が 1.3 以下のデバイスでは、スレッドごとに 16KiB のローカル メモリしか利用できません。これは、ユーザーpart_of_frameやその他の中間データには十分ではない可能性があります。しかし、計算能力 >= 2.0 では、これは 512KiB に拡張され、十分なはずです。

于 2012-07-18T21:07:50.093 に答える
5

あなたの質問のいくつかに答えます:

カーネルの呼び出しはそれほど高価ではないため、プログラム フローが GPU から CPU に戻ることを恐れないでください。結果を GPU メモリに保持する限り、大きなオーバーヘッドは発生しません。必要に応じて、他のデバイス関数を順番に呼び出すだけのカーネルを作成できます。私の知る限り、これはデバッグとプロファイルが難しくなります。カーネルによって呼び出される関数をプロファイルできるかどうかはわかりません。

並列化について:

複数のデータ ストリームで計算を実行できるアイデアはどれも良いと思います。コードがシェーダーに似ているほど優れています (つまり、GPU で高速に実行するために必要な特性を備えていることになります)。複数のフレームを使用するアイデアは素晴らしいです。それに関するいくつかのヒント: 同期を可能な限り最小限に抑える、メモリへのアクセスを可能な限り少なくする、計算時間と IO 要求時間の比率を増やすようにする、GPU レジスタ/共有メモリを利用する、1 つから複数の読み取りを優先する1対多の設計。

于 2012-07-18T20:16:45.577 に答える
1

GPU リソースが 1 つのカーネルで 3 つの関数を処理するのに十分な場合は、関数を大きなカーネルに配置するか、3 つのカーネルを連続して起動して関数を個別に実行できます。カーネル起動のハードウェア オーバーヘッドはごくわずかで、ソフトウェア オーバーヘッドは低いため、パフォーマンスに関してはほとんど違いがありません。

ただし、GPU リソースが十分でない場合、1 つのカーネルに 3 つの関数を配置すると、パフォーマンスが犠牲になる可能性があります。この場合、各関数を個別のカーネルに配置することをお勧めします。

于 2012-07-18T20:41:11.990 に答える