チュートリアル: C++ AMP アプリケーションのデバッグ
この記事では、C++ Accelerated Massive Parallelism (C++ AMP) を使用してグラフィックス処理装置 (GPU) を活用するアプリケーションをデバッグする方法について説明します。 ここでは、大きな整数配列を合計する並列リダクション プログラムを使用します。 このチュートリアルでは、次の作業について説明します。
- GPU デバッガーを起動する。
- [GPU スレッド] ウィンドウで GPU スレッドを調べる。
- [並列スタック] ウィンドウを使用して、複数の GPU スレッドの呼び出し履歴を同時に観察する。
- [並列ウォッチ] ウィンドウを使用して、同時に複数のスレッドにわたって 1 つの式の値を調べる。
- GPU スレッドのフラグ設定、凍結、凍結解除、およびグループ化を行う。
- タイルのすべてのスレッドをコード内の特定の位置まで実行する。
前提条件
このチュートリアルを開始する前に、以下を行ってください。
Note
C++ AMP ヘッダーは、Visual Studio 2022 バージョン 17.0 以降では非推奨です。
AMP ヘッダーを含めると、ビルド エラーが発生します。 警告をサイレント状態にするには、AMP ヘッダーを含める前に _SILENCE_AMP_DEPRECATION_WARNINGS
を定義します。
- 「C++ AMP の概要」を読む。
- 行番号がテキスト エディターに表示されていることを確認する。 詳細については、「方法: エディターで行番号を表示する」を参照してください。
- ソフトウェア エミュレーターでのデバッグをサポートするために、Windows 8 または Windows Server 2012 以降が実行されていることを確認する。
Note
次の手順で参照している Visual Studio ユーザー インターフェイス要素の一部は、お使いのコンピューターでは名前や場所が異なる場合があります。 これらの要素は、使用している Visual Studio のエディションや独自の設定によって決まります。 詳細については、「IDE をカスタマイズする」をご覧ください。
サンプル プロジェクトを作成するには
プロジェクトを作成する手順は、使用している Visual Studio のバージョンによって異なります。 このページの目次の上で、正しいドキュメント バージョンが選択されていることを確認してください。
Visual Studio でサンプル プロジェクトを作成するには
メニューバーで、 [ファイル]>[新規作成]>[プロジェクト] の順に選択して、 [新しいプロジェクトの作成] ダイアログ ボックスを開きます。
ダイアログの上部で、[言語] を [C++] に、[プラットフォーム] を [Windows] に、[プロジェクト タイプ] を [コンソール] に設定します。
フィルター処理されたプロジェクト タイプの一覧から、 [コンソール アプリ] を選択して、 [次へ] を選択します。 次のページで、[名前] ボックスに「
AMPMapReduce
」と入力してプロジェクトの名前を指定し、プロジェクトの場所を変更する場合はその場所を指定します。[作成] ボタンを選択してクライアント プロジェクトを作成します。
Visual Studio 2017 または Visual Studio 2015 でサンプル プロジェクトを作成するには
Visual Studio を起動します。
メニュー バーで、 [ファイル]>[新規作成]>[プロジェクト] を選択します。
テンプレート ペインの [インストール済み] で、[Visual C++] を選択します。
[Win32 コンソール アプリケーション] を選択し、[名前] ボックスに「
AMPMapReduce
」と入力して、[OK] ボタンを選択します。[次へ] ボタンをクリックします。
[プリコンパイル済みヘッダー] チェック ボックスをオフにし、[完了] ボタンを選択します。
ソリューション エクスプローラーで、プロジェクトから stdafx.h、targetver.h、および stdafx.cpp を削除します。
次:
AMPMapReduce.cpp を開き、内容を次のコードで置き換えます。
// AMPMapReduce.cpp defines the entry point for the program. // The program performs a parallel-sum reduction that computes the sum of an array of integers. #include <stdio.h> #include <tchar.h> #include <amp.h> const int BLOCK_DIM = 32; using namespace concurrency; void sum_kernel_tiled(tiled_index<BLOCK_DIM> t_idx, array<int, 1> &A, int stride_size) restrict(amp) { tile_static int localA[BLOCK_DIM]; index<1> globalIdx = t_idx.global * stride_size; index<1> localIdx = t_idx.local; localA[localIdx[0]] = A[globalIdx]; t_idx.barrier.wait(); // Aggregate all elements in one tile into the first element. for (int i = BLOCK_DIM / 2; i > 0; i /= 2) { if (localIdx[0] < i) { localA[localIdx[0]] += localA[localIdx[0] + i]; } t_idx.barrier.wait(); } if (localIdx[0] == 0) { A[globalIdx] = localA[0]; } } int size_after_padding(int n) { // The extent might have to be slightly bigger than num_stride to // be evenly divisible by BLOCK_DIM. You can do this by padding with zeros. // The calculation to do this is BLOCK_DIM * ceil(n / BLOCK_DIM) return ((n - 1) / BLOCK_DIM + 1) * BLOCK_DIM; } int reduction_sum_gpu_kernel(array<int, 1> input) { int len = input.extent[0]; //Tree-based reduction control that uses the CPU. for (int stride_size = 1; stride_size < len; stride_size *= BLOCK_DIM) { // Number of useful values in the array, given the current // stride size. int num_strides = len / stride_size; extent<1> e(size_after_padding(num_strides)); // The sum kernel that uses the GPU. parallel_for_each(extent<1>(e).tile<BLOCK_DIM>(), [&input, stride_size] (tiled_index<BLOCK_DIM> idx) restrict(amp) { sum_kernel_tiled(idx, input, stride_size); }); } array_view<int, 1> output = input.section(extent<1>(1)); return output[0]; } int cpu_sum(const std::vector<int> &arr) { int sum = 0; for (size_t i = 0; i < arr.size(); i++) { sum += arr[i]; } return sum; } std::vector<int> rand_vector(unsigned int size) { srand(2011); std::vector<int> vec(size); for (size_t i = 0; i < size; i++) { vec[i] = rand(); } return vec; } array<int, 1> vector_to_array(const std::vector<int> &vec) { array<int, 1> arr(vec.size()); copy(vec.begin(), vec.end(), arr); return arr; } int _tmain(int argc, _TCHAR* argv[]) { std::vector<int> vec = rand_vector(10000); array<int, 1> arr = vector_to_array(vec); int expected = cpu_sum(vec); int actual = reduction_sum_gpu_kernel(arr); bool passed = (expected == actual); if (!passed) { printf("Actual (GPU): %d, Expected (CPU): %d", actual, expected); } printf("sum: %s\n", passed ? "Passed!" : "Failed!"); getchar(); return 0; }
メニュー バーで、[ファイル]>[すべてを保存] の順に選択します。
ソリューション エクスプローラーで、AMPMapReduce のショートカット メニューを開き、[プロパティ] を選択します。
[プロパティ ページ] ダイアログ ボックスの [構成プロパティ] で、[C/C++]>[プリコンパイル済みヘッダー] の順に選択します。
[プリコンパイル済みヘッダー] プロパティでは、[プリコンパイル済みヘッダーを使用しない] を選択し、[OK] ボタンを選択します。
メニュー バーで、 [ビルド]>[ソリューションのビルド] の順にクリックします。
CPU コードをデバッグする
この手順では、ローカル Windows デバッガーを使用して、このアプリケーションの CPU コードが適切であることを確認します。 このアプリケーションの CPU コードで特に注目するべきセグメントは、reduction_sum_gpu_kernel
関数内の for
ループです。 これによって、GPU で実行されるツリーベースの並列リダクションが制御されます。
CPU コードをデバッグするには
ソリューション エクスプローラーで、AMPMapReduce のショートカット メニューを開き、[プロパティ] を選択します。
[プロパティ ページ] ダイアログ ボックスの [構成プロパティ] で、[デバッグ] を選択します。 [起動するデバッガー] ボックスの一覧で [ローカル Windows デバッガー] が選択されていることを確認します。
コード エディターに戻ります。
次の図に示すコード行にブレークポイントを設定します (およそ 67 行目と 70 行目)。
CPU ブレークポイントメニュー バーで、[デバッグ]>[デバッグ開始] の順に選択します。
[ローカル] ウィンドウで、70 行目のブレークポイントに達するまで、
stride_size
の値を観察します。メニュー バーで、[デバッグ]>[デバッグの停止] の順に選択します。
GPU コードをデバッグする
このセクションでは、GPU コードをデバッグする方法について説明します。これは sum_kernel_tiled
関数が含まれているコードです。 GPU コードでは、各 "ブロック" の整数の合計を並列で計算します。
GPU コードをデバッグするには
ソリューション エクスプローラーで、AMPMapReduce のショートカット メニューを開き、[プロパティ] を選択します。
[プロパティ ページ] ダイアログ ボックスの [構成プロパティ] で、[デバッグ] を選択します。
[起動するデバッガー] の一覧で、[ローカル Windows デバッガー] を選択します。
[デバッガーの種類] ボックスの一覧で、[自動] が選択されていることを確認します。
[自動] は既定値です。 Windows 10 より前のバージョンでは、[自動] ではなく [GPU のみ] が必要な値です。
OK を選択します。
次の図に示すように、30 行目にブレークポイントを設定します。
GPU ブレークポイントメニュー バーで、[デバッグ]>[デバッグ開始] の順に選択します。 67 行目と 70 行目の CPU コードのブレークポイントは、GPU のデバッグ中には実行されません。これらのコード行は CPU に対して実行されるためです。
[GPU スレッド] ウィンドウを使用するには
[GPU スレッド] ウィンドウを開くには、メニュー バーで [デバッグ]>[Windows]>[GPU スレッド] の順に選択します。
表示される [GPU スレッド] ウィンドウで、GPU スレッドの状態を調べることができます。
[GPU スレッド] ウィンドウを Visual Studio の下部にドッキングします。 [Expand Thread Switch]\(スレッドの切り替えの展開\) ボタンを選択すると、タイルとスレッドのテキスト ボックスが表示されます。 [GPU スレッド] ウィンドウには、次の図に示すように、アクティブな GPU スレッドとブロックされている GPU スレッドの合計数が表示されます。
[GPU スレッド] ウィンドウこの計算には、313 のタイルが割り当てられます。 各タイルには 32 のスレッドが含まれています。 ローカル GPU のデバッグはソフトウェア エミュレーターで行われるため、4 つのアクティブな GPU スレッドがあります。 4 つのスレッドが同時に命令を実行し、まとまって次の命令へと進みます。
[GPU スレッド] ウィンドウでは、4 つの GPU スレッドがアクティブになっており、21 行目あたり (
t_idx.barrier.wait();
) で定義されている tile_barrier::wait ステートメントで 28 個の GPU スレッドがブロックされています。 32 個の GPU スレッドはすべて、最初のタイルであるtile[0]
に属します。 矢印は、現在のスレッドが含まれる行を指しています。 別のスレッドに切り替えるには、次のいずれかの方法を使用します。[GPU スレッド] ウィンドウで、切り替え先のスレッドの行でショートカット メニューを開き、[スレッドに切り替え] を選択します。 行が複数のスレッドを表している場合は、スレッド座標に従って最初のスレッドに切り替えます。
対応するテキスト ボックスにスレッドのタイルとスレッドの値を入力し、[スレッドの切り替え] ボタンを選択します。
[呼び出し履歴] ウィンドウに、現在の GPU スレッドの呼び出し履歴が表示されます。
[並列スタック] ウィンドウを使用するには
[並列スタック] ウィンドウを開くには、メニュー バーで [デバッグ]>[Windows]>[並列スタック] の順に選択します。
[並列スタック] ウィンドウを使用すると、複数の GPU スレッドのスタック フレームを同時に調べることができます。
[並列スタック] ウィンドウを Visual Studio の下部にドッキングします。
左上隅の一覧で [スレッド] が選択されていることを確認します。 次の図の [並列スタック] ウィンドウには、[GPU スレッド] ウィンドウに表示されていた GPU スレッドの呼び出し履歴にフォーカスしたビューが表示されます。
[並列スタック] ウィンドウ32 個のスレッドは、
_kernel_stub
からparallel_for_each
関数呼び出しのラムダ ステートメントまで進んで、次にsum_kernel_tiled
関数まで進み、ここで並列リダクションが行われています。 32 個のスレッドのうち 28 個がtile_barrier::wait
ステートメントまで進み、22 行目でブロックされたままになります。一方、他の 4 つのスレッドは 30 行目のsum_kernel_tiled
関数でアクティブなままとなっています。GPU スレッドのプロパティを調べることができます。 これらは、[並列スタック] ウィンドウ内に表示される [GPU スレッド] ウィンドウの豊富なデータヒントで提供されます。 これらを表示するには、
sum_kernel_tiled
のスタック フレーム上にポインターを合わせます。 次の図に、データヒントを示します。
GPU スレッドのデータヒント[並列スタック] ウィンドウの詳細については、[並列スタック] ウィンドウの使用に関するページを参照してください。
[並列ウォッチ] ウィンドウを使用するには
[並列ウォッチ] ウィンドウを開くには、メニュー バーで [デバッグ]>[Windows]>[並列ウォッチ]>[並列ウォッチ 1] の順に選択します。
[並列ウォッチ] ウィンドウを使用すると、複数のスレッドにわたって 1 つの式の値を調べることができます。
[並列ウォッチ 1] ウィンドウを Visual Studio の下部にドッキングします。 [並列ウォッチ] ウィンドウのテーブルには 32 行が表示されています。 各行は、[GPU スレッド] ウィンドウと [並列スタック] ウィンドウの両方に表示されていた GPU スレッドに対応しています。 ここでは、32 個の GPU スレッドすべてにわたって値を調べる式を入力できます。
[ウォッチの追加] 列ヘッダーを選択し、「
localIdx
」と入力して、Enter キーを押します。もう一度 [ウォッチの追加] 列ヘッダーを選択し、「
globalIdx
」と入力して、Enter キーを押します。もう一度 [ウォッチの追加] 列ヘッダーを選択し、「
localA[localIdx[0]]
」と入力して、Enter キーを押します。対応する列ヘッダーを選択すると、指定した式で並べ替えることができます。
localA[localIdx[0]] 列ヘッダーを選択して、列を並べ替えます。 次の図は、localA[localIdx[0]] で並べ替えた結果を示しています。
並べ替えの結果[Excel] ボタンを選択し、[Excel で開く] を選択することによって、[並列ウォッチ] ウィンドウの内容を Excle にエクスポートできます。 開発用コンピューターに Excel がインストールされている場合、このボタンを選択すると、この内容が含まれた Excel ワークシートが開きます。
[並列ウォッチ] ウィンドウの右上隅にあるフィルター コントロールでは、ブール式を使用して内容をフィルター処理できます。 フィルター コントロールのテキスト ボックスに「
localA[localIdx[0]] > 20000
」と入力し、Enter キーを押します。このウィンドウには、
localA[localIdx[0]]
値が 20000 を超えるスレッドだけが含まれるようになります。 内容は、localA[localIdx[0]]
列で並べ替え (前に選択した並べ替えアクション) られたままになっています。
GPU スレッドにフラグを設定する
[GPU スレッド] ウィンドウ、[並列ウォッチ] ウィンドウ、または [並列スタック] ウィンドウのデータヒントで特定の GPU スレッドにフラグを設定することによって、マークを付けることができます。 [GPU スレッド] ウィンドウの行に複数のスレッドが含まれている場合、その行にフラグを設定すると、その行に含まれるすべてのスレッドにフラグが設定されます。
GPU スレッドにフラグを設定するには
[並列ウォッチ 1] ウィンドウで [スレッド] 列ヘッダーを選択して、タイル インデックスおよびスレッド インデックスで並べ替えます。
メニュー バーで [デバッグ]>[続行] の順に選択すると、アクティブだった 4 つのスレッドが次のバリア (AMPMapReduce.cpp の 32 行目で定義) に進みます。
現在アクティブになっている 4 つのスレッドを含む行の左側にあるフラグ シンボルを選択します。
次の図は、[GPU スレッド] ウィンドウの、フラグが設定された 4 つのアクティブなスレッドを示しています。
[GPU スレッド] ウィンドウのアクティブなスレッド[並列ウォッチ] ウィンドウと、[並列スタック] ウィンドウのデータヒントのいずれにも、フラグが設定されたスレッドが表示されます。
フラグを設定した 4 つのスレッドにフォーカスする場合は、フラグが設定されたスレッドのみを表示するように選択できます。 これにより、[GPU スレッド]、[並列ウォッチ]、および [並列スタック] ウィンドウの表示内容が制限されます。
いずれかのウィンドウまたは [デバッグの場所] ツール バーの [フラグが設定されているスレッドのみを表示] ボタンを選択します。 次の図は、[デバッグの場所] ツール バーの [フラグが設定されているスレッドのみを表示] ボタンを示しています。
[フラグが設定されているスレッドのみを表示] ボタンこれで、[GPU スレッド]、[並列ウォッチ]、および [並列スタック] の各ウィンドウに、フラグが設定されているスレッドのみが表示されます。
GPU スレッドを凍結および凍結解除する
GPU スレッドの凍結 (一時停止) と凍結解除 (再開) は、[GPU スレッド] ウィンドウまたは [並列ウォッチ] ウィンドウから行うことができます。 CPU スレッドの凍結と凍結解除は同じように行うことができます。詳細については、[スレッド] ウィンドウの使用方法に関する記事を参照してください。
GPU スレッドを凍結および凍結解除するには
[フラグが設定されているスレッドのみを表示] ボタンを選択して、すべてのスレッドを表示します。
メニュー バーで [デバッグ]>[続行] の順に選択します。
アクティブな行でショートカット メニューを開き、[凍結] を選択します。
次の [GPU スレッド] ウィンドウの図は、4 つのスレッドすべてが凍結されていることを示しています。
[GPU スレッド] ウィンドウの凍結されたスレッド同様に、[並列ウォッチ] ウィンドウにも、4 つのスレッドすべてが凍結されていることが示されます。
メニュー バーで [デバッグ]>[続行] を選択すると、次の 4 つの GPU スレッドが 22 行目のバリアを超えて、30 行目のブレークポイントまで進むことができます。 [GPU スレッド] ウィンドウには、前に凍結された 4 つのスレッドが、アクティブな状態で凍結されたままであることが示されます。
メニュー バーで [デバッグ]、[続行] の順に選択します。
[並列ウォッチ] ウィンドウでは、個別または複数の GPU スレッドを凍結解除することもできます。
GPU スレッドをグループ化するには
[GPU スレッド] ウィンドウのいずれかのスレッドのショートカット メニューで、[グループ化]、[アドレス] の順に選択します。
[GPU スレッド] ウィンドウのスレッドが、アドレスでグループ化されます。 アドレスは、スレッドの各グループが配置されている逆アセンブリの命令に対応しています。 24 個のスレッドが 22 行目にあり、ここでは tile_barrier::Wait メソッドが実行されます。 12 個のスレッドが、32 行目のバリアの命令のところにあります。 これらのスレッドのうち 4 つには、フラグが設定されています。 8 個のスレッドが、30 行目のブレークポイントにあります。 これらのスレッドのうち 4 つは凍結されています。 次の図は、[GPU スレッド] ウィンドウのグループ化されたスレッドを示しています。
[GPU スレッド] ウィンドウのグループ化されたスレッド[並列ウォッチ] ウィンドウのデータ グリッドのショートカット メニューを開いて、グループ化操作を行うこともできます。 [グループ化] を選択し、スレッドをグループ化する方法に対応したメニュー項目を選択します。
すべてのスレッドをコード内の特定の場所まで実行する
特定のタイル内のすべてのスレッドを、カーソルが含まれる行まで実行するには、[現在の Tile をカーソル行の前まで実行] を使用します。
すべてのスレッドをカーソルでマークされた場所まで実行するには
凍結されているスレッドのショートカット メニューで、[凍結解除] を選択します。
コード エディターで、カーソルを 30 行目に置きます。
コード エディターのショートカット メニューで、[現在の Tile をカーソル行の前まで実行] を選択します。
前に 21 行目のバリアでブロックされていた 24 個のスレッドが、32 行目まで進みます。 これが [GPU スレッド] ウィンドウに表示されます。
関連項目
C++ AMP の概要
GPU コードのデバッグ
方法: [GPU スレッド] ウィンドウを使用する
方法: [並列ウォッチ] ウィンドウを使用する
コンカレンシー ビジュアライザーによる C++ AMP コードの分析