逐步解說:偵錯C++ AMP 應用程式
本文示範如何偵錯使用C++加速大規模平行處理原則 (C++ AMP) 來利用圖形處理單元 (GPU) 的應用程式。 它會使用平行縮減程式來加總大量的整數陣列。 本逐步解說將說明下列工作:
- 啟動 GPU 調試程式。
- 在 [GPU 線程] 視窗中檢查 GPU 線程。
- 使用 [ 平行堆棧 ] 視窗,同時觀察多個 GPU 線程的呼叫堆疊。
- 使用 [ 平行監看 式] 視窗,同時檢查跨多個線程的單一表達式值。
- 標記、凍結、解除凍結和群組 GPU 線程。
- 將磚的所有線程執行到程序代碼中的特定位置。
必要條件
開始本逐步解說之前:
注意
從 Visual Studio 2022 17.0 版開始,C++ AMP 標頭已被取代。
包含任何 AMP 標頭將會產生建置錯誤。 先定義 _SILENCE_AMP_DEPRECATION_WARNINGS
,再包含任何 AMP 標頭以讓警告無聲。
- 閱讀 C++ AMP 概觀。
- 請確定文字編輯器中會顯示行號。 如需詳細資訊,請參閱 如何:在編輯器中顯示行號。
- 請確定您至少執行 Windows 8 或 Windows Server 2012,以支援軟體模擬器上的偵錯。
注意
在下列指示的某些 Visual Studio 使用者介面項目中,您的電腦可能會顯示不同的名稱或位置: 您所擁有的 Visual Studio 版本以及使用的設定會決定這些項目。 如需詳細資訊,請參閱將 Visual Studio IDE 個人化。
建立範例專案
建立專案的指示會根據您使用的 Visual Studio 版本而有所不同。 請確定您已在此頁面上選取正確的檔版本。
在 Visual Studio 中建立範例專案
在功能表列上,選擇 [檔案]>[新增]>[專案],以開啟 [建立新專案] 對話方塊。
在對話方塊頂端,將 [語言] 設定為 C++,將 [平台] 設定為 Windows,並將 [專案類型] 設定為主控台。
從專案類型的篩選清單中,選擇 [主控台應用程式],然後選擇 [下一步]。 在下一頁中,於 [名稱] 方塊中輸入
AMPMapReduce
,以指定項目的名稱,並視需要不同的專案位置指定專案位置。選擇 [建立] 按鈕以建立用戶端專案。
在 Visual Studio 2017 或 Visual Studio 2015 中建立範例專案
啟動 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 程式代碼的區段特別有趣,就是 for
函式中的 reduction_sum_gpu_kernel
迴圈。 它會控制在 GPU 上執行的樹狀結構型平行縮減。
偵錯 CPU 程式代碼
在 方案總管 中,開啟 AMPMapReduce 的快捷方式功能表,然後選擇 [屬性]。
在 [屬性頁] 對話框的 [組態屬性] 底下,選擇 [偵錯]。 確認已在 [調試程式] 中選取 [要啟動的本機 Windows 調試程式] 清單。
返回程式 代碼編輯器。
在下圖所示的程式代碼行上設定斷點(大約第 67 行 70 行)。
CPU 中斷點在功能表列上,依序選擇 [偵錯]>[開始偵錯]。
在 [ 局部變數 ] 視窗中,觀察 的值
stride_size
,直到到達第 70 行的斷點為止。在功能表列上,依序選擇 [偵錯]>[停止偵錯]。
偵錯 GPU 程式代碼
本節說明如何偵錯 GPU 程式代碼,這是函式中包含的 sum_kernel_tiled
程序代碼。 GPU 程式代碼會平行計算每個「區塊」的整數總和。
偵錯 GPU 程式代碼
在 方案總管 中,開啟 AMPMapReduce 的快捷方式功能表,然後選擇 [屬性]。
在 [屬性頁] 對話框的 [組態屬性] 底下,選擇 [偵錯]。
在 [要啟動的偵錯工具] 清單中,選取 [本機 Windows 偵錯工具]。
在 [ 調試程序類型] 列表中,確認已 選取 [自動 ]。
Auto 是預設值。 在 Windows 10 之前的版本中, GPU 僅限 是必要值,而不是 Auto。
選擇 [確定] 按鈕。
在第 30 行設定斷點,如下圖所示。
GPU 斷點在功能表列上,依序選擇 [偵錯]>[開始偵錯]。 CPU 程式代碼在第 67 行和 70 行的斷點不會在 GPU 偵錯期間執行,因為這些程式代碼行會在 CPU 上執行。
使用 [GPU 線程] 視窗
若要開啟 [GPU 線程] 視窗,請在功能表欄上,選擇 [偵>錯 Windows>GPU 線程]。
您可以在出現的 [GPU 線程] 視窗中檢查 GPU 線程的狀態。
將 [GPU 線程] 視窗停駐在 Visual Studio 底部。 選擇 [ 展開線程切換 ] 按鈕以顯示磚和線程文字框。 [ GPU 線程] 視窗會顯示作用中和封鎖的 GPU 線程總數,如下圖所示。
[GPU 執行緒] 視窗313 個磚會配置給此計算。 每個圖格都包含32個線程。 由於本機 GPU 偵錯發生在軟體模擬器上,因此有四個作用中的 GPU 線程。 這四個線程會同時執行指令,然後一起移至下一個指令。
在 [GPU 線程] 視窗中,在大約第 21
t_idx.barrier.wait();
行定義的 tile_barrier::wait 語句中,有四個 GPU 線程作用中,28 個 GPU 線程遭到封鎖。 所有 32 個 GPU 線程都屬於第一個磚tile[0]
。 箭號指向包含目前線程的數據列。 若要切換至不同的線程,請使用下列其中一種方法:在線程切換至 [GPU 線程 ] 視窗中的數據列中,開啟快捷方式功能表,然後選擇 [ 切換至線程]。 如果數據列代表一個以上的線程,您將根據線程座標切換至第一個線程。
在對應的文字框中輸入線程的圖格和線程值,然後選擇 [ 切換線程] 按鈕。
[ 呼叫堆疊 ] 視窗會顯示目前 GPU 線程的呼叫堆疊。
使用 [平行堆疊] 視窗
若要開啟 [平行堆棧] 視窗,請在功能表欄上,選擇 [偵>錯 Windows>平行堆棧]。
您可以使用 [ 平行堆棧 ] 視窗,同時檢查多個 GPU 線程的堆疊框架。
將 [平行堆棧] 視窗停駐在 Visual Studio 底部。
請確定 已在左上角的清單中選取 [線程 ]。 在下圖中,[ 平行堆棧 ] 視窗會顯示您在 [GPU 線程] 視窗中看到 之 GPU 線程 的呼叫堆棧焦點檢視。
[平行堆疊] 視窗32 個線程會從函式呼叫中的
parallel_for_each
Lambda 語句,然後移至_kernel_stub
sum_kernel_tiled
函式,其中會發生平行縮減。 32 個線程中有 28 個已進展至 語句,tile_barrier::wait
並在第 22 行保持封鎖,而其他四個線程則保留在函式中sum_kernel_tiled
,第 30 行。您可以檢查 GPU 線程的屬性。 其可在 [平行堆棧] 視窗的豐富數據提示的 [GPU 線程] 視窗中取得。 若要查看它們,請將指標暫留在的
sum_kernel_tiled
堆疊框架上。 下圖顯示DataTip。
GPU 線程數據提示如需平行堆疊視窗的詳細資訊,請參閱使用平行堆棧視窗。
使用 [平行監看式] 視窗
若要開啟 [平行監看式] 視窗,請在功能表欄上,選擇 [偵>錯 Windows>平行監看式平行監看>式 1]。
您可以使用 [ 平行監看 式] 視窗來檢查跨多個線程的運算式值。
將 [平行監看式 1] 視窗停駐在 Visual Studio 底部。 [平行監看式] 視窗數據表中有 32 個數據列。 每個都對應至 GPU 線程視窗和 平行堆疊 視窗中顯示的 GPU 線程。 現在,您可以輸入想要在所有 32 個 GPU 線程中檢查其值的運算式。
選取 [新增監看式] 數據行標頭,輸入 ,然後選擇Enter
localIdx
鍵。再次選取 [ 新增監看 式] 數據行標頭,輸入
globalIdx
,然後選擇 Enter 鍵。再次選取 [ 新增監看 式] 數據行標頭,輸入
localA[localIdx[0]]
,然後選擇 Enter 鍵。您可以藉由選取其對應的數據行標頭,依指定的表達式排序。
選取 localA[localIdx[0]] 資料行標頭來排序數據行。 下圖顯示依 localA[localIdx[0]] 排序的結果。
排序結果您可以選擇 [Excel] 按鈕,然後選擇 [在 Excel 中開啟],將 [平行監看式] 視窗中的內容匯出至 Excel。 如果您已在開發計算機上安裝 Excel,按鈕會開啟包含內容的 Excel 工作表。
在 [平行監看式] 視窗右上角,有一個篩選控件,您可以使用布爾表達式來篩選內容。 在篩選控件文本框中輸入
localA[localIdx[0]] > 20000
,然後選擇 Enter 鍵。窗口現在只包含值大於 20000 的
localA[localIdx[0]]
線程。 內容仍會依localA[localIdx[0]]
數據行排序,也就是您稍早選擇的排序動作。
標記 GPU 線程
您可以在 [GPU 線程] 視窗、[平行監看式] 視窗或 [平行堆棧] 視窗中的 [數據提示] 視窗中標記特定 GPU 線程。 如果 [GPU 線程] 視窗中的數據列包含一個以上的線程,則標示該數據列會標幟數據列中包含的所有線程。
為 GPU 線程加上旗標
在 [平行監看式 1] 視窗中選取 [線程] 數據行標頭,依磚索引和線程索引排序。
在功能表欄上,選擇 >[偵錯繼續],這會導致作用中的四個線程進入下一個屏障(定義於第 32 行AMPMapReduce.cpp)。
選擇數據列左側的旗標符號,其中包含目前作用中的四個線程。
下圖顯示 [GPU 線程] 視窗中的四個作用中 標幟線程 。
[GPU 執行緒] 視窗中正在活動的執行緒[平行監看式] 視窗和 [平行堆棧] 視窗的 [數據提示] 都表示已標幟的線程。
如果您想要將焦點放在標示的四個線程上,您可以選擇只顯示已標示的線程。 它會限制您在 GPU 線程、平行監看式和平行堆疊視窗中看到的內容。
選擇任何視窗或 [偵錯位置] 工具列上的 [僅顯示標幟] 按鈕。 下圖顯示 [偵錯位置] 工具列上的 [僅顯示標幟] 按鈕。
顯示 [僅標幟] 按鈕現在,GPU 線程、平行監看和平行堆疊視窗只會顯示標幟的線程。
凍結和解除凍結 GPU 線程
您可以從 [GPU 線程] 視窗或 [平行監看式] 視窗凍結 (暫停) 和解除凍結 (繼續) GPU 線程。 您可以凍結和解除凍結 CPU 線程的方式相同;如需詳細資訊,請參閱 如何:使用線程視窗。
凍結和解除凍結 GPU 線程
選擇 [ 僅顯示標幟] 按鈕以顯示所有線程。
在功能表欄上,選擇 [偵錯>繼續]。
開啟使用中數據列的快捷方式功能表,然後選擇 [ 凍結]。
[GPU 線程] 視窗的下圖顯示所有四個線程都已凍結。
GPU 線程視窗中的凍結線程同樣地,[ 平行監看 式] 視窗會顯示所有四個線程都已凍結。
在功能表欄上,選擇 >[偵錯繼續] 以允許接下來的四個 GPU 線程經過第 22 行的屏障,並在第 30 行到達斷點。 [ GPU 線程] 視窗會顯示四個先前凍結的線程會維持凍結狀態,且處於作用中狀態。
在功能表欄上,選擇 [偵錯]、[繼續]。
從 [ 平行監看 式] 視窗中,您也可以解除凍結個別或多個 GPU 線程。
將 GPU 線程分組
在 [GPU 線程] 視窗中其中一個線程的快捷方式功能表上,選擇 [分組依據]、[位址]。
[GPU 線程] 視窗中的線程會依位址分組。 地址會對應至反組譯碼中每個線程群組所在的指令。 24 個線程位於執行tile_barrier::wait 方法的第 22 行。 12 個線程位於第 32 行屏障的指示中。 其中四個線程會標幟。 八個線程位於第 30 行的斷點。 其中四個線程已凍結。 下圖顯示 [GPU 線程] 視窗中的群組線程。
GPU 線程視窗中的群組線程您也可以開啟 [平行監看式] 視窗數據格的快捷方式功能表,以執行 [分組依據] 作業。 選取 [ 群組依據],然後選擇對應至您要如何群組線程的功能表項。
將所有線程執行至程序代碼中的特定位置
您可以使用 [執行目前磚至資料指標],將指定磚中的所有線程執行到包含游標的行。
若要將所有線程執行至數據指標所標示的位置
在凍結線程的快捷方式功能表上,選擇 [解除凍結]。
在程式代碼編輯器中,將游標放在第 30 行。
在 [程序代碼編輯器] 的快捷方式功能表上,選擇 [執行目前磚至游標]。
先前在第 21 行的屏障上封鎖的 24 個線程已進入第 32 行。 它會顯示在 [GPU 線程] 視窗中。
另請參閱
C++ AMP 概觀
偵錯 GPU 程式代碼
如何:使用 GPU 執行緒視窗
如何:使用平行監看式視窗
使用並行可視化檢視分析C++ AMP 程序代碼