チュートリアル: 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 でサンプル プロジェクトを作成するには

  1. メニューバーで、 [ファイル]>[新規作成]>[プロジェクト] の順に選択して、 [新しいプロジェクトの作成] ダイアログ ボックスを開きます。

  2. ダイアログの上部で、[言語][C++] に、[プラットフォーム][Windows] に、[プロジェクト タイプ][コンソール] に設定します。

  3. フィルター処理されたプロジェクト タイプの一覧から、 [コンソール アプリ] を選択して、 [次へ] を選択します。 次のページで、[名前] ボックスに「AMPMapReduce」と入力してプロジェクトの名前を指定し、プロジェクトの場所を変更する場合はその場所を指定します。

    Screenshot showing the Create a new project dialog with the Console App template selected.

  4. [作成] ボタンを選択してクライアント プロジェクトを作成します。

Visual Studio 2017 または Visual Studio 2015 でサンプル プロジェクトを作成するには

  1. Visual Studio を起動します。

  2. メニュー バーで、 [ファイル]>[新規作成]>[プロジェクト] を選択します。

  3. テンプレート ペインの [インストール済み] で、[Visual C++] を選択します。

  4. [Win32 コンソール アプリケーション] を選択し、[名前] ボックスに「AMPMapReduce」と入力して、[OK] ボタンを選択します。

  5. [次へ] ボタンをクリックします。

  6. [プリコンパイル済みヘッダー] チェック ボックスをオフにし、[完了] ボタンを選択します。

  7. ソリューション エクスプローラーで、プロジェクトから stdafx.htargetver.h、および stdafx.cpp を削除します。

次:

  1. 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;
    }
    
  2. メニュー バーで、[ファイル]>[すべてを保存] の順に選択します。

  3. ソリューション エクスプローラーで、AMPMapReduce のショートカット メニューを開き、[プロパティ] を選択します。

  4. [プロパティ ページ] ダイアログ ボックスの [構成プロパティ] で、[C/C++]>[プリコンパイル済みヘッダー] の順に選択します。

  5. [プリコンパイル済みヘッダー] プロパティでは、[プリコンパイル済みヘッダーを使用しない] を選択し、[OK] ボタンを選択します。

  6. メニュー バーで、 [ビルド]>[ソリューションのビルド] の順にクリックします。

CPU コードをデバッグする

この手順では、ローカル Windows デバッガーを使用して、このアプリケーションの CPU コードが適切であることを確認します。 このアプリケーションの CPU コードで特に注目するべきセグメントは、reduction_sum_gpu_kernel 関数内の for ループです。 これによって、GPU で実行されるツリーベースの並列リダクションが制御されます。

CPU コードをデバッグするには

  1. ソリューション エクスプローラーで、AMPMapReduce のショートカット メニューを開き、[プロパティ] を選択します。

  2. [プロパティ ページ] ダイアログ ボックスの [構成プロパティ] で、[デバッグ] を選択します。 [起動するデバッガー] ボックスの一覧で [ローカル Windows デバッガー] が選択されていることを確認します。

  3. コード エディターに戻ります。

  4. 次の図に示すコード行にブレークポイントを設定します (およそ 67 行目と 70 行目)。

    CPU breakpoints marked next to lines of code in the editor.
    CPU ブレークポイント

  5. メニュー バーで、[デバッグ]>[デバッグ開始] の順に選択します。

  6. [ローカル] ウィンドウで、70 行目のブレークポイントに達するまで、stride_size の値を観察します。

  7. メニュー バーで、[デバッグ]>[デバッグの停止] の順に選択します。

GPU コードをデバッグする

このセクションでは、GPU コードをデバッグする方法について説明します。これは sum_kernel_tiled 関数が含まれているコードです。 GPU コードでは、各 "ブロック" の整数の合計を並列で計算します。

GPU コードをデバッグするには

  1. ソリューション エクスプローラーで、AMPMapReduce のショートカット メニューを開き、[プロパティ] を選択します。

  2. [プロパティ ページ] ダイアログ ボックスの [構成プロパティ] で、[デバッグ] を選択します。

  3. [起動するデバッガー] の一覧で、[ローカル Windows デバッガー] を選択します。

  4. [デバッガーの種類] ボックスの一覧で、[自動] が選択されていることを確認します。

    [自動] は既定値です。 Windows 10 より前のバージョンでは、[自動] ではなく [GPU のみ] が必要な値です。

  5. OK を選択します。

  6. 次の図に示すように、30 行目にブレークポイントを設定します。

    GPU breakpoints marked next to a line of code in the editor.
    GPU ブレークポイント

  7. メニュー バーで、[デバッグ]>[デバッグ開始] の順に選択します。 67 行目と 70 行目の CPU コードのブレークポイントは、GPU のデバッグ中には実行されません。これらのコード行は CPU に対して実行されるためです。

[GPU スレッド] ウィンドウを使用するには

  1. [GPU スレッド] ウィンドウを開くには、メニュー バーで [デバッグ]>[Windows]>[GPU スレッド] の順に選択します。

    表示される [GPU スレッド] ウィンドウで、GPU スレッドの状態を調べることができます。

  2. [GPU スレッド] ウィンドウを Visual Studio の下部にドッキングします。 [Expand Thread Switch]\(スレッドの切り替えの展開\) ボタンを選択すると、タイルとスレッドのテキスト ボックスが表示されます。 [GPU スレッド] ウィンドウには、次の図に示すように、アクティブな GPU スレッドとブロックされている GPU スレッドの合計数が表示されます。

    GPU Threads window with 4 active threads.
    [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 スレッドの呼び出し履歴が表示されます。

[並列スタック] ウィンドウを使用するには

  1. [並列スタック] ウィンドウを開くには、メニュー バーで [デバッグ]>[Windows]>[並列スタック] の順に選択します。

    [並列スタック] ウィンドウを使用すると、複数の GPU スレッドのスタック フレームを同時に調べることができます。

  2. [並列スタック] ウィンドウを Visual Studio の下部にドッキングします。

  3. 左上隅の一覧で [スレッド] が選択されていることを確認します。 次の図の [並列スタック] ウィンドウには、[GPU スレッド] ウィンドウに表示されていた GPU スレッドの呼び出し履歴にフォーカスしたビューが表示されます。

    Parallel Stacks window with 4 active threads.
    [並列スタック] ウィンドウ

    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 のスタック フレーム上にポインターを合わせます。 次の図に、データヒントを示します。

    DataTip for Parallel Stacks window.
    GPU スレッドのデータヒント

    [並列スタック] ウィンドウの詳細については、[並列スタック] ウィンドウの使用に関するページを参照してください。

[並列ウォッチ] ウィンドウを使用するには

  1. [並列ウォッチ] ウィンドウを開くには、メニュー バーで [デバッグ]>[Windows]>[並列ウォッチ]>[並列ウォッチ 1] の順に選択します。

    [並列ウォッチ] ウィンドウを使用すると、複数のスレッドにわたって 1 つの式の値を調べることができます。

  2. [並列ウォッチ 1] ウィンドウを Visual Studio の下部にドッキングします。 [並列ウォッチ] ウィンドウのテーブルには 32 行が表示されています。 各行は、[GPU スレッド] ウィンドウと [並列スタック] ウィンドウの両方に表示されていた GPU スレッドに対応しています。 ここでは、32 個の GPU スレッドすべてにわたって値を調べる式を入力できます。

  3. [ウォッチの追加] 列ヘッダーを選択し、「localIdx」と入力して、Enter キーを押します。

  4. もう一度 [ウォッチの追加] 列ヘッダーを選択し、「globalIdx」と入力して、Enter キーを押します。

  5. もう一度 [ウォッチの追加] 列ヘッダーを選択し、「localA[localIdx[0]]」と入力して、Enter キーを押します。

    対応する列ヘッダーを選択すると、指定した式で並べ替えることができます。

    localA[localIdx[0]] 列ヘッダーを選択して、列を並べ替えます。 次の図は、localA[localIdx[0]] で並べ替えた結果を示しています。

    Parallel Watch window with sorted results.
    並べ替えの結果

    [Excel] ボタンを選択し、[Excel で開く] を選択することによって、[並列ウォッチ] ウィンドウの内容を Excle にエクスポートできます。 開発用コンピューターに Excel がインストールされている場合、このボタンを選択すると、この内容が含まれた Excel ワークシートが開きます。

  6. [並列ウォッチ] ウィンドウの右上隅にあるフィルター コントロールでは、ブール式を使用して内容をフィルター処理できます。 フィルター コントロールのテキスト ボックスに「localA[localIdx[0]] > 20000」と入力し、Enter キーを押します。

    このウィンドウには、localA[localIdx[0]] 値が 20000 を超えるスレッドだけが含まれるようになります。 内容は、localA[localIdx[0]] 列で並べ替え (前に選択した並べ替えアクション) られたままになっています。

GPU スレッドにフラグを設定する

[GPU スレッド] ウィンドウ、[並列ウォッチ] ウィンドウ、または [並列スタック] ウィンドウのデータヒントで特定の GPU スレッドにフラグを設定することによって、マークを付けることができます。 [GPU スレッド] ウィンドウの行に複数のスレッドが含まれている場合、その行にフラグを設定すると、その行に含まれるすべてのスレッドにフラグが設定されます。

GPU スレッドにフラグを設定するには

  1. [並列ウォッチ 1] ウィンドウで [スレッド] 列ヘッダーを選択して、タイル インデックスおよびスレッド インデックスで並べ替えます。

  2. メニュー バーで [デバッグ]>[続行] の順に選択すると、アクティブだった 4 つのスレッドが次のバリア (AMPMapReduce.cpp の 32 行目で定義) に進みます。

  3. 現在アクティブになっている 4 つのスレッドを含む行の左側にあるフラグ シンボルを選択します。

    次の図は、[GPU スレッド] ウィンドウの、フラグが設定された 4 つのアクティブなスレッドを示しています。

    GPU Threads window with flagged threads.
    [GPU スレッド] ウィンドウのアクティブなスレッド

    [並列ウォッチ] ウィンドウと、[並列スタック] ウィンドウのデータヒントのいずれにも、フラグが設定されたスレッドが表示されます。

  4. フラグを設定した 4 つのスレッドにフォーカスする場合は、フラグが設定されたスレッドのみを表示するように選択できます。 これにより、[GPU スレッド][並列ウォッチ]、および [並列スタック] ウィンドウの表示内容が制限されます。

    いずれかのウィンドウまたは [デバッグの場所] ツール バーの [フラグが設定されているスレッドのみを表示] ボタンを選択します。 次の図は、[デバッグの場所] ツール バーの [フラグが設定されているスレッドのみを表示] ボタンを示しています。

    Debug Location toolbar with Show Only Flagged icon.
    [フラグが設定されているスレッドのみを表示] ボタン

    これで、[GPU スレッド][並列ウォッチ]、および [並列スタック] の各ウィンドウに、フラグが設定されているスレッドのみが表示されます。

GPU スレッドを凍結および凍結解除する

GPU スレッドの凍結 (一時停止) と凍結解除 (再開) は、[GPU スレッド] ウィンドウまたは [並列ウォッチ] ウィンドウから行うことができます。 CPU スレッドの凍結と凍結解除は同じように行うことができます。詳細については、[スレッド] ウィンドウの使用方法に関する記事を参照してください。

GPU スレッドを凍結および凍結解除するには

  1. [フラグが設定されているスレッドのみを表示] ボタンを選択して、すべてのスレッドを表示します。

  2. メニュー バーで [デバッグ]>[続行] の順に選択します。

  3. アクティブな行でショートカット メニューを開き、[凍結] を選択します。

    次の [GPU スレッド] ウィンドウの図は、4 つのスレッドすべてが凍結されていることを示しています。

    GPU Threads windows showing frozen threads.
    [GPU スレッド] ウィンドウの凍結されたスレッド

    同様に、[並列ウォッチ] ウィンドウにも、4 つのスレッドすべてが凍結されていることが示されます。

  4. メニュー バーで [デバッグ]>[続行] を選択すると、次の 4 つの GPU スレッドが 22 行目のバリアを超えて、30 行目のブレークポイントまで進むことができます。 [GPU スレッド] ウィンドウには、前に凍結された 4 つのスレッドが、アクティブな状態で凍結されたままであることが示されます。

  5. メニュー バーで [デバッグ][続行] の順に選択します。

  6. [並列ウォッチ] ウィンドウでは、個別または複数の GPU スレッドを凍結解除することもできます。

GPU スレッドをグループ化するには

  1. [GPU スレッド] ウィンドウのいずれかのスレッドのショートカット メニューで、[グループ化][アドレス] の順に選択します。

    [GPU スレッド] ウィンドウのスレッドが、アドレスでグループ化されます。 アドレスは、スレッドの各グループが配置されている逆アセンブリの命令に対応しています。 24 個のスレッドが 22 行目にあり、ここでは tile_barrier::Wait メソッドが実行されます。 12 個のスレッドが、32 行目のバリアの命令のところにあります。 これらのスレッドのうち 4 つには、フラグが設定されています。 8 個のスレッドが、30 行目のブレークポイントにあります。 これらのスレッドのうち 4 つは凍結されています。 次の図は、[GPU スレッド] ウィンドウのグループ化されたスレッドを示しています。

    GPU Threads window with threads grouped by Address.
    [GPU スレッド] ウィンドウのグループ化されたスレッド

  2. [並列ウォッチ] ウィンドウのデータ グリッドのショートカット メニューを開いて、グループ化操作を行うこともできます。 [グループ化] を選択し、スレッドをグループ化する方法に対応したメニュー項目を選択します。

すべてのスレッドをコード内の特定の場所まで実行する

特定のタイル内のすべてのスレッドを、カーソルが含まれる行まで実行するには、[現在の Tile をカーソル行の前まで実行] を使用します。

すべてのスレッドをカーソルでマークされた場所まで実行するには

  1. 凍結されているスレッドのショートカット メニューで、[凍結解除] を選択します。

  2. コード エディターで、カーソルを 30 行目に置きます。

  3. コード エディターのショートカット メニューで、[現在の Tile をカーソル行の前まで実行] を選択します。

    前に 21 行目のバリアでブロックされていた 24 個のスレッドが、32 行目まで進みます。 これが [GPU スレッド] ウィンドウに表示されます。

関連項目

C++ AMP の概要
GPU コードのデバッグ
方法: [GPU スレッド] ウィンドウを使用する
方法: [並列ウォッチ] ウィンドウを使用する
コンカレンシー ビジュアライザーによる C++ AMP コードの分析