演练:调试 C++ AMP 应用程序
本主题演示如何调试使用C++加速的大量并行的应用程序(C++ AMP)利用处理单元(GPU)的图像。 它使用总结大整数的并行降低程序。 本演练阐释了以下任务:
生成GPU调试器。
检查在GPU的GPU线程线程窗口。
使用"并行堆栈"窗口同时观察调用堆栈多个GPU线程。
使用并行"监视"窗口检查单个表达式的值跨多个线程同时的。
标记,冻结,解冻和分组GPU线程。
执行个平铺的所有线程到特定位置的代码。
系统必备
在开始本演练之前:
读取 C++ AMP 概述。
确保行号在文本编辑器中显示。 有关更多信息,请参见 如何:在编辑器中显示行号。
确定正在运行 Windows 8 或 Windows Server 2012 支持在软件模拟器上调试。
备注
对于在以下说明中使用的某些 Visual Studio 用户界面元素,您的计算机可能会显示不同的名称或位置。这些元素取决于您所使用的 Visual Studio 版本和您所使用的设置。有关更多信息,请参见 Visual Studio 设置。
创建示例项目
启动 Visual Studio。
在菜单栏上,依次选择 *** 文件 ***,新建,项目。
在"模板"窗格中 *** 安装 *** 下,选择 *** Visual C++ ***。
选择 *** Win32控制台应用程序 ***,请在 名称 框中 AMPMapReduce,然后选择 *** 好 *** 按钮。
选择**“下一步”**按钮。
清除 *** 预编译标头 *** 复选框,然后选择 *** 完成 *** 按钮。
在 *** 解决方案资源管理器 ***,删除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++ ***,*** 预编译标头 ***。
为 *** 预编译标头 *** 属性,选择" *** 使用预编译标头,则 ***,然后选择 *** 好 *** 按钮。
在菜单栏上,依次选择 *** 生成 ***,*** 生成解决方案 ***。
调试CPU代码
在此过程中,您将使用本地Windows调试器,以确保此应用程序的CPU代码是正确的。 CPU代码段在特别感兴趣的此应用程序的是在 reduction_sum_gpu_kernel 功能的 for 循环。 该控件在GPU运行的基于树的并行降低。
调试CPU代码
在 *** 解决方案资源管理器 ***,打开 ***** AMPMapReduce *****的快捷菜单,然后选择 属性。
在 *** 属性页 *** 对话框中,在 ***** 配置属性 *****下,选择 *** 调试 ***。 验证 *** 本地Windows调试器 *** 在 *** 生成的调试器 *** 列表中选择项。
返回代码编辑器。
设置在下图所示的代码行中设置断点(大致第67行70)。
CPU断点
在菜单栏上,依次选择**“调试”、“启动调试”**。
在 *** 本地 *** "窗口中,将 stride_size 观察该值,直到在行70的断点为止。
在菜单栏上,依次选择 *** 调试 ***,*** 停止调试 ***。
调试GPU代码
本节演示如何调试GPU代码中,是在 sum_kernel_tiled 函数包含的代码。 GPU代码计算整数之和每个“块”并行。
调试GPU代码
在 *** 解决方案资源管理器 ***,打开 ***** AMPMapReduce *****的快捷菜单,然后选择 属性。
在 *** 属性页 *** 对话框中,在 ***** 配置属性 *****下,选择 *** 调试 ***。
在 *** 生成的调试器 *** 列表中,选择 *** 本地Windows调试器 ***。
在 *** 调试器类型 *** 列表中,选择 *** 仅GPU ***。
选择**“确定”**按钮。
如下图所示,设置断点位于第30行,。
GPU断点
在菜单栏上,依次选择**“调试”、“启动调试”**。 因为这些代码行在CPU,是在CPU代码中设置断点在67和70行不是在GPU调试过程中。
使用GPU "线程"窗口
若要打开GPU "线程"窗口,在菜单栏上,选择 *** 调试 ***,*** Windows ***,*** GPU线程 ***。
您可以检查GPU的GPU线程线程将出现窗口的状态。
停靠GPU "线程"窗口在Visual Studio底部。 选择 *** 展开线程切换 *** 按钮显示平铺和线程文本框。 如下图所示,GPU "线程"窗口显示活动和阻塞的GPU线程的总数,。
GPU线程"窗口
具有313平铺分配给此计算。 每个平铺包含32个线程。 由于本地GPU调试在软件模拟器发生,具有四个有效的GPU线程。 四个线程同时执行命令。然后继续下一条指令。
在GPU "线程"窗口,具有有效四GPU的线程和28 GPU线程将阻塞 tile_barrier::wait 语句大约定义位于第21行(t_idx.barrier.wait();)。 所有32 GPU线程所属的第一个平铺,tile[0]。 箭头指向包含当前线程的行。 向不同的线程切换,请使用下列方法之一:
在线程的行切换到GPU "线程"窗口,请打开快捷菜单中选择 *** 线程切换 ***。 如果行表示多个线程,则将切换到第一个线程根据线程坐标。
输入平铺、进程和线程的值在相应的文本框的然后选择 *** 切换线程 *** 按钮。
调用堆栈"窗口显示当前GPU线程的调用堆栈。
使用"并行堆栈"窗口
若要打开"并行堆栈"窗口,在菜单栏上,选择 *** 调试 ***,*** Windows ***,*** 并行堆栈 ***。
可以使用"并行堆栈"窗口同时检查多个GPU线程堆栈帧。
停靠"并行堆栈"窗口在Visual Studio底部。
确保 *** 线程 *** 在左上角的列表中选择。 在下图中,并行堆栈"窗口显示在GPU "线程"窗口显示GPU一个线程的调用堆栈集中视图。
“并行堆栈”窗口
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线程数据提示
有关并行堆栈"窗口的更多信息,请参见 使用“并行堆栈”窗口。
使用并行"监视"窗口
若要打开并行"监视"窗口,在菜单栏上,选择 *** 调试 ***,*** Windows ***,*** 并行监视 ***,*** 并行监视1 ***。
可以使用并行"监视"窗口检查某个表达式的值在多个线程中。
停靠并行监视1的windows到Visual Studio底部。 具有32行在并行"监视"窗口的表中。 每个对应于出现在GPU线程"窗口和"并行堆栈"窗口的GPU线程。 现在,可以输入值您希望在所有32 GPU线程中检查表达式。
选择 *** 添加监视 *** 列标头,输入 localIdx,然后选择enter键。
再次选择 *** 添加监视 *** 列标头,键入 globalIdx,然后选择enter键。
再次选择 *** 添加监视 *** 列标头,键入 localA [localIdx 0] [],然后选择enter键。
您可以通过一个指定的表达式排序通过选择相应的列标头。
选择 *** localA [localIdx 0] [] *** 列标头对列进行排序。 下图显示由 ***** localA [localIdx 0] [] *****排序的结果。
结果排序
可以导出到Excel在并行"监视"窗口的内容通过选择Excel按钮然后选择 *** 在Excel中打开 ***。 如果您的开发计算机上安装Excel时,这将打开包含内容的Excel工作表。
在并行"监视"窗口的右上角,可通过使用布尔表达式,可用于筛选该目录的筛选器控件。 输入 localA [localIdx 0] [] > 20000 在筛选器控件文本框然后选择enter键。
窗口现在包含 localA[localIdx[0]] 值大于20000仅的线程。 该内容由 localA[localIdx[0]] 列仍排序,这是排序的事件在运行之前。
标记的GPU线程
可以通过将它们标记特定GPU线程在GPU "线程"窗口、并行监视窗口或数据提示"并行堆栈"窗口。 如果在GPU线程"窗口中的行包含多个线程,标记行标记行中包含的所有线程。
标记GPU线程
选择在并行1 "监视"窗口的 *** [线程] *** 列标头排序平铺索引和线程索引。
在菜单栏上,依次选择 *** 调试 ***,*** 继续 ***,导致四个线程处于活动状态继续执行到下一个障碍(定义在行AMPMapReduce.cpp 32)。
在包含四个线程现在处于活动状态的行的左侧选择标志符号。
下面的插图GPU "线程"窗口显示了有效标记的线程。
在GPU的活动线程线程"窗口
并行"监视"窗口和"并行堆栈"窗口的数据提示两个指示标记的线程。
如果您要关注您标记的四个线程,则在GPU线程、并行"和"并行堆栈"窗口可以选择显示,因此,只有标记的线程。
选择仅显示标记为的按钮在任何窗口或在 *** "调试位置 *** 工具栏。 下图显示了在 *** "调试位置 *** 工具栏中仅显示标记为的按钮。
仅显示标记的按钮
现在GPU线程、并行"和"并行堆栈"窗口仅显示标记的线程。
冻结和解冻GPU线程
可以冻结(挂起),而解冻(继续)从GPU线程窗口或"并行"监视"窗口的GPU线程。 可以冻结和解冻线程CPU相同的方式;有关信息,请参见 如何:使用“线程”窗口。
冻结和解冻GPU线程
选择 *** 仅显示标记 *** 按钮可以显示所有线程。
在菜单栏上,依次选择 *** 调试 ***,*** 继续 ***。
打开有效的行的快捷菜单中选择 *** 冻结 ***。
GPU线程"窗口中的下图演示,所有四线程已冻结。
在GPU的冻结线程的线程"窗口
同样,并行"监视"窗口中,所有四线程已冻结。
在菜单栏上,依次选择 *** 调试 ***,*** 继续 *** 允许下四GPU线程继续通过关卡在行22和到达断点位于第30行。 GPU线程窗口中,四以前冻结线程保持冻结和在活动状态。
在菜单栏上,依次选择 *** 调试 ***,*** 继续 ***。
从并行"监视"窗口,还可以解冻单个或多个GPU线程。
分组GPU线程
在之一的快捷菜单在 *** GPU线程 *** 窗口的线程,选择 *** 组 ***,*** 地址 ***。
地址分组在GPU线程"窗口中的线程。 该地址对应于找到线程每个组的反汇编的命令。 24个线程在第22 tile_barrier::wait 方法 执行的位置。 12线程处于障碍的命令位于第32行。 四这些线程标记。 八个线程在断点位于第30行。 四这些线程已冻结。 下面的插图GPU "线程"窗口显示分组线程。
在GPU的分组线程线程"窗口
您可以通过打开,选择 *** 组 ***,然后选择对应的菜单项的并行"监视"窗口的数据网格的快捷菜单还执行 *** 组 *** 操作希望如何分组线程。
运行所有线程对代码的特定位置
您运行中给出的所有线程平铺如何使用 *** 运行当前平铺到光标 ***,包含光标的行。
运行所有线程。游标指示的位置
在冻结线程的快捷菜单上,选择 *** 解冻 ***。
在代码编辑器中,将光标置于行30。
在代码编辑器快捷菜单,选择 *** 运行当前平铺到光标 ***。
以前块位于行21的障碍的24个线程继续执行到第32行。 这在 *** GPU线程 *** 显示窗口。