次の方法で共有


チュートリアル: C++ AMP アプリケーションのデバッグ

この記事では、C++ Accelerated Massive Parallelism (C++ AMP) を使用してグラフィックス処理装置 (GPU) を活用するアプリケーションをデバッグする方法について説明します。 ここでは、大きな整数配列を合計する並列リダクション プログラムを使用します。 このチュートリアルでは、次の作業について説明します。

  • GPU デバッガーを起動する。
  • [GPU スレッド] ウィンドウで GPU スレッドを調べる。
  • [並列スタック] ウィンドウを使用して、複数の GPU スレッドの呼び出し履歴を同時に観察する。
  • [並列ウォッチ] ウィンドウを使用して、同時に複数のスレッドにわたって 1 つの式の値を調べる。
  • GPU スレッドのフラグ設定、凍結、凍結解除、およびグループ化を行う。
  • タイルのすべてのスレッドをコード内の特定の位置まで実行する。

前提条件

このチュートリアルを開始する前に、以下を行ってください。

C++ AMP ヘッダーは、Visual Studio 2022 バージョン 17.0 以降では非推奨です。 AMP ヘッダーを含めると、ビルド エラーが発生します。 警告をサイレント状態にするには、AMP ヘッダーを含める前に _SILENCE_AMP_DEPRECATION_WARNINGS を定義します。

  • C++ AMP の概要」を読む。
  • 行番号がテキスト エディターに表示されていることを確認する。 詳細については、「方法: エディターで行番号を表示する」を参照してください。
  • ソフトウェア エミュレーターでのデバッグをサポートするために、Windows 8 または Windows Server 2012 以降が実行されていることを確認する。

次の手順で参照している Visual Studio ユーザー インターフェイス要素の一部は、お使いのコンピューターでは名前や場所が異なる場合があります。 これらの要素は、使用している Visual Studio のエディションや独自の設定によって決まります。 詳細については、「IDE をカスタマイズする」をご覧ください。

サンプル プロジェクトを作成するには

プロジェクトを作成する手順は、使用している Visual Studio のバージョンによって異なります。 このページの目次の上で、正しいドキュメント バージョンが選択されていることを確認してください。

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

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

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

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

    [コンソール アプリ] テンプレートが選択された [新しいプロジェクトの作成] ダイアログを示すスクリーンショット。

  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 コードで特に注目するべきセグメントは、for 関数内の reduction_sum_gpu_kernel ループです。 これによって、GPU で実行されるツリーベースの並列リダクションが制御されます。

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

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

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

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

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

    エディターのコード行の横にマークされた CPU ブレークポイント。
    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 ブレークポイント。
    GPU ブレークポイント

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

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

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

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

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

    アクティブなスレッドが 4 つの GPU スレッド ウィンドウ。
    [GPU スレッド] ウィンドウ

    この計算には、313 のタイルが割り当てられます。 各タイルには 32 のスレッドが含まれています。 ローカル GPU のデバッグはソフトウェア エミュレーターで行われるため、4 つのアクティブな GPU スレッドがあります。 4 つのスレッドが同時に命令を実行し、まとまって次の命令へと進みます。

    [GPU スレッド] ウィンドウでは、4 つの GPU スレッドがアクティブになっており、21 行目あたり () で定義されている t_idx.barrier.wait(); ステートメントで 28 個の GPU スレッドがブロックされています。 32 個の GPU スレッドはすべて、最初のタイルである tile[0] に属します。 矢印は、現在のスレッドが含まれる行を指しています。 別のスレッドに切り替えるには、次のいずれかの方法を使用します。

    • [GPU スレッド] ウィンドウで、切り替え先のスレッドの行でショートカット メニューを開き、[スレッドに切り替え] を選択します。 行が複数のスレッドを表している場合は、スレッド座標に従って最初のスレッドに切り替えます。

    • 対応するテキスト ボックスにスレッドのタイルとスレッドの値を入力し、[スレッドの切り替え] ボタンを選択します。

    [呼び出し履歴] ウィンドウに、現在の GPU スレッドの呼び出し履歴が表示されます。

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

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

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

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

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

    アクティブなスレッドが 4 つの [並列スタック] ウィンドウ。
    [並列スタック] ウィンドウ

    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 スレッドのデータヒント

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

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

  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]] で並べ替えた結果を示しています。

    結果が並べ替えられた [並列ウォッチ] ウィンドウ。
    並べ替えの結果

    [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 スレッド ウィンドウ。
    [GPU スレッド] ウィンドウのアクティブなスレッド

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

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

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

    [フラグ付きのみ表示] アイコンが表示された [デバッグの場所] ツール バー。
    [フラグが設定されているスレッドのみを表示] ボタン

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

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

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

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

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

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

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

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

    固定されたスレッドを示す GPU スレッド ウィンドウ。
    [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 スレッド] ウィンドウ。
    [GPU スレッド] ウィンドウのグループ化されたスレッド

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

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

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

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

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

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

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

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

関連項目

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