演练:调试 C++ AMP 应用程序

本主题演示如何调试使用C++加速的大量并行的应用程序(C++ AMP)利用处理单元(GPU)的图像。 它使用总结大整数的并行降低程序。 本演练阐释了以下任务:

  • 生成GPU调试器。

  • 检查在GPU的GPU线程线程窗口。

  • 使用"并行堆栈"窗口同时观察调用堆栈多个GPU线程。

  • 使用并行"监视"窗口检查单个表达式的值跨多个线程同时的。

  • 标记,冻结,解冻和分组GPU线程。

  • 执行个平铺的所有线程到特定位置的代码。

系统必备

在开始本演练之前:

备注

对于在以下说明中使用的某些 Visual Studio 用户界面元素,您的计算机可能会显示不同的名称或位置。这些元素取决于您所使用的 Visual Studio 版本和您所使用的设置。有关更多信息,请参见 Visual Studio 设置

创建示例项目

  1. 启动 Visual Studio。

  2. 在菜单栏上,依次选择 *** 文件 ***新建项目

  3. 在"模板"窗格中 *** 安装 *** 下,选择 *** Visual C++ ***

  4. 选择 *** Win32控制台应用程序 ***,请在 名称 框中 AMPMapReduce,然后选择 *** 好 *** 按钮。

  5. 选择**“下一步”**按钮。

  6. 清除 *** 预编译标头 *** 复选框,然后选择 *** 完成 *** 按钮。

  7. *** 解决方案资源管理器 ***,删除stdafx.h、targetver.h和stdafx.cpp从项目。

  8. 打开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;
    }
    
  9. 在菜单栏上,依次选择 *** 文件 ****** "全部保存 ***

  10. *** 解决方案资源管理器 ***,打开 ***** AMPMapReduce *****的快捷菜单,然后选择 属性

  11. *** 属性页 *** 对话框中,在 *** 配置属性 ***下,选择 *** C/C++ ****** 预编译标头 ***

  12. *** 预编译标头 *** 属性,选择" *** 使用预编译标头,则 ***,然后选择 *** 好 *** 按钮。

  13. 在菜单栏上,依次选择 *** 生成 ****** 生成解决方案 ***

调试CPU代码

在此过程中,您将使用本地Windows调试器,以确保此应用程序的CPU代码是正确的。 CPU代码段在特别感兴趣的此应用程序的是在 reduction_sum_gpu_kernel 功能的 for 循环。 该控件在GPU运行的基于树的并行降低。

调试CPU代码

  1. *** 解决方案资源管理器 ***,打开 ***** AMPMapReduce *****的快捷菜单,然后选择 属性

  2. *** 属性页 *** 对话框中,在 ***** 配置属性 *****下,选择 *** 调试 ***。 验证 *** 本地Windows调试器 ****** 生成的调试器 *** 列表中选择项。

  3. 返回代码编辑器。

  4. 设置在下图所示的代码行中设置断点(大致第67行70)。

    CPU断点

    CPU 断点

  5. 在菜单栏上,依次选择**“调试”“启动调试”**。

  6. *** 本地 *** "窗口中,将 stride_size 观察该值,直到在行70的断点为止。

  7. 在菜单栏上,依次选择 *** 调试 ****** 停止调试 ***

调试GPU代码

本节演示如何调试GPU代码中,是在 sum_kernel_tiled 函数包含的代码。 GPU代码计算整数之和每个“块”并行。

调试GPU代码

  1. *** 解决方案资源管理器 ***,打开 ***** AMPMapReduce *****的快捷菜单,然后选择 属性

  2. *** 属性页 *** 对话框中,在 ***** 配置属性 *****下,选择 *** 调试 ***

  3. *** 生成的调试器 *** 列表中,选择 *** 本地Windows调试器 ***

  4. *** 调试器类型 *** 列表中,选择 *** 仅GPU ***

  5. 选择**“确定”**按钮。

  6. 如下图所示,设置断点位于第30行,。

    GPU断点

    GPU 断点

  7. 在菜单栏上,依次选择**“调试”“启动调试”**。 因为这些代码行在CPU,是在CPU代码中设置断点在67和70行不是在GPU调试过程中。

使用GPU "线程"窗口

  1. 若要打开GPU "线程"窗口,在菜单栏上,选择 *** 调试 ****** Windows ****** GPU线程 ***

    您可以检查GPU的GPU线程线程将出现窗口的状态。

  2. 停靠GPU "线程"窗口在Visual Studio底部。 选择 *** 展开线程切换 *** 按钮显示平铺和线程文本框。 如下图所示,GPU "线程"窗口显示活动和阻塞的GPU线程的总数,。

    GPU线程"窗口

    包含 4 个活动线程的 GPU 线程窗口

    具有313平铺分配给此计算。 每个平铺包含32个线程。 由于本地GPU调试在软件模拟器发生,具有四个有效的GPU线程。 四个线程同时执行命令。然后继续下一条指令。

    在GPU "线程"窗口,具有有效四GPU的线程和28 GPU线程将阻塞 tile_barrier::wait 语句大约定义位于第21行(t_idx.barrier.wait();)。 所有32 GPU线程所属的第一个平铺,tile[0]。 箭头指向包含当前线程的行。 向不同的线程切换,请使用下列方法之一:

    • 在线程的行切换到GPU "线程"窗口,请打开快捷菜单中选择 *** 线程切换 ***。 如果行表示多个线程,则将切换到第一个线程根据线程坐标。

    • 输入平铺、进程和线程的值在相应的文本框的然后选择 *** 切换线程 *** 按钮。

    调用堆栈"窗口显示当前GPU线程的调用堆栈。

使用"并行堆栈"窗口

  1. 若要打开"并行堆栈"窗口,在菜单栏上,选择 *** 调试 ****** Windows ****** 并行堆栈 ***

    可以使用"并行堆栈"窗口同时检查多个GPU线程堆栈帧。

  2. 停靠"并行堆栈"窗口在Visual Studio底部。

  3. 确保 *** 线程 *** 在左上角的列表中选择。 在下图中,并行堆栈"窗口显示在GPU "线程"窗口显示GPU一个线程的调用堆栈集中视图。

    “并行堆栈”窗口

    包含 4 个活动线程的并行堆栈窗口

    32个线程从 _kernel_stub 转至 parallel_for_each 的语句lambda函数调用再到 sum_kernel_tiled 功能,并行降低发生。 28从32个线程继续到 tile_barrier::wait 语句并保持被在行22,4,而其他线程在 sum_kernel_tiled 函数一直保持活动位于第30行。

    可以检查可在并行堆栈"窗口的丰富的数据提示中的GPU "线程"窗口GPU线程的属性。 为此,请将在 ***** sum_kernel_tiled *****堆栈帧的鼠标指针。 下图显示数据提示。

    GPU线程数据提示

    并行堆栈窗口的数据提示

    有关并行堆栈"窗口的更多信息,请参见 使用“并行堆栈”窗口

使用并行"监视"窗口

  1. 若要打开并行"监视"窗口,在菜单栏上,选择 *** 调试 ****** Windows ****** 并行监视 ****** 并行监视1 ***

    可以使用并行"监视"窗口检查某个表达式的值在多个线程中。

  2. 停靠并行监视1的windows到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按钮然后选择 *** 在Excel中打开 ***。 如果您的开发计算机上安装Excel时,这将打开包含内容的Excel工作表。

  6. 在并行"监视"窗口的右上角,可通过使用布尔表达式,可用于筛选该目录的筛选器控件。 输入 localA [localIdx 0] [] > 20000 在筛选器控件文本框然后选择enter键。

    窗口现在包含 localA[localIdx[0]] 值大于20000仅的线程。 该内容由 localA[localIdx[0]] 列仍排序,这是排序的事件在运行之前。

标记的GPU线程

可以通过将它们标记特定GPU线程在GPU "线程"窗口、并行监视窗口或数据提示"并行堆栈"窗口。 如果在GPU线程"窗口中的行包含多个线程,标记行标记行中包含的所有线程。

标记GPU线程

  1. 选择在并行1 "监视"窗口的 *** [线程] *** 列标头排序平铺索引和线程索引。

  2. 在菜单栏上,依次选择 *** 调试 ****** 继续 ***,导致四个线程处于活动状态继续执行到下一个障碍(定义在行AMPMapReduce.cpp 32)。

  3. 在包含四个线程现在处于活动状态的行的左侧选择标志符号。

    下面的插图GPU "线程"窗口显示了有效标记的线程。

    在GPU的活动线程线程"窗口

    包含已标记线程的 GPU 线程窗口

    并行"监视"窗口和"并行堆栈"窗口的数据提示两个指示标记的线程。

  4. 如果您要关注您标记的四个线程,则在GPU线程、并行"和"并行堆栈"窗口可以选择显示,因此,只有标记的线程。

    选择仅显示标记为的按钮在任何窗口或在 *** "调试位置 *** 工具栏。 下图显示了在 *** "调试位置 *** 工具栏中仅显示标记为的按钮。

    仅显示标记的按钮

    具有“仅显示已标记项”图标的“调试位置”工具栏

    现在GPU线程、并行"和"并行堆栈"窗口仅显示标记的线程。

冻结和解冻GPU线程

可以冻结(挂起),而解冻(继续)从GPU线程窗口或"并行"监视"窗口的GPU线程。 可以冻结和解冻线程CPU相同的方式;有关信息,请参见 如何:使用“线程”窗口

冻结和解冻GPU线程

  1. 选择 *** 仅显示标记 *** 按钮可以显示所有线程。

  2. 在菜单栏上,依次选择 *** 调试 ****** 继续 ***

  3. 打开有效的行的快捷菜单中选择 *** 冻结 ***

    GPU线程"窗口中的下图演示,所有四线程已冻结。

    在GPU的冻结线程的线程"窗口

    显示已冻结线程的 GPU 线程窗口

    同样,并行"监视"窗口中,所有四线程已冻结。

  4. 在菜单栏上,依次选择 *** 调试 ****** 继续 *** 允许下四GPU线程继续通过关卡在行22和到达断点位于第30行。 GPU线程窗口中,四以前冻结线程保持冻结和在活动状态。

  5. 在菜单栏上,依次选择 *** 调试 ****** 继续 ***

  6. 从并行"监视"窗口,还可以解冻单个或多个GPU线程。

分组GPU线程

  1. 在之一的快捷菜单在 *** GPU线程 *** 窗口的线程,选择 *** 组 ****** 地址 ***

    地址分组在GPU线程"窗口中的线程。 该地址对应于找到线程每个组的反汇编的命令。 24个线程在第22 tile_barrier::wait 方法 执行的位置。 12线程处于障碍的命令位于第32行。 四这些线程标记。 八个线程在断点位于第30行。 四这些线程已冻结。 下面的插图GPU "线程"窗口显示分组线程。

    在GPU的分组线程线程"窗口

    其中的线程按地址进行分组的 GPU 线程窗口

  2. 您可以通过打开,选择 *** 组 ***,然后选择对应的菜单项的并行"监视"窗口的数据网格的快捷菜单还执行 *** 组 *** 操作希望如何分组线程。

运行所有线程对代码的特定位置

您运行中给出的所有线程平铺如何使用 *** 运行当前平铺到光标 ***,包含光标的行。

运行所有线程。游标指示的位置

  1. 在冻结线程的快捷菜单上,选择 *** 解冻 ***

  2. 在代码编辑器中,将光标置于行30。

  3. 在代码编辑器快捷菜单,选择 *** 运行当前平铺到光标 ***

    以前块位于行21的障碍的24个线程继续执行到第32行。 这在 *** GPU线程 *** 显示窗口。

请参见

任务

如何:使用“GPU 线程”窗口

如何:使用“并行监视”窗口

概念

C++ AMP 概述

其他资源

调试 GPU 代码

分析C++的并发可视化工具的AMP代码