Przewodnik: debugowanie aplikacji C++ AMP
W tym artykule pokazano, jak debugować aplikację korzystającą z przyspieszonego równoległości C++ (C++ Massive Parallelism) w celu korzystania z jednostki przetwarzania grafiki (GPU). Używa on programu redukcji równoległej, który sumuje dużą tablicę liczb całkowitych. W instruktażu przedstawiono następujące zagadnienia:
- Uruchamianie debugera procesora GPU.
- Sprawdzanie wątków procesora GPU w oknie Wątki procesora GPU.
- Korzystając z okna Stosy równoległe, można jednocześnie obserwować stosy wywołań wielu wątków procesora GPU.
- Korzystając z okna Zegarek równoległy, można sprawdzić wartości pojedynczego wyrażenia w wielu wątkach w tym samym czasie.
- Flagowanie, zamrażanie, rozmrażanie i grupowanie wątków procesora GPU.
- Wykonywanie wszystkich wątków kafelka do określonej lokalizacji w kodzie.
Wymagania wstępne
Przed rozpoczęciem tego przewodnika:
Uwaga
Nagłówki C++ AMP są przestarzałe, począwszy od programu Visual Studio 2022 w wersji 17.0.
Dołączenie wszystkich nagłówków AMP spowoduje wygenerowanie błędów kompilacji. Zdefiniuj _SILENCE_AMP_DEPRECATION_WARNINGS
przed dołączeniem żadnych nagłówków AMP, aby wyciszyć ostrzeżenia.
- Przeczytaj omówienie C++ AMP.
- Upewnij się, że numery wierszy są wyświetlane w edytorze tekstów. Aby uzyskać więcej informacji, zobacz How to: Display line numbers in the editor (Instrukcje: wyświetlanie numerów wierszy w edytorze).
- Upewnij się, że używasz co najmniej systemu Windows 8 lub Windows Server 2012 do obsługi debugowania w emulatorze oprogramowania.
Uwaga
Na komputerze w poniższych instrukcjach mogą być wyświetlane inne nazwy i lokalizacje niektórych elementów interfejsu użytkownika programu Visual Studio. Te elementy są określane przez numer wersji Visual Studio oraz twoje ustawienia. Aby uzyskać więcej informacji, zobacz Personalizowanie środowiska IDE.
Aby utworzyć przykładowy projekt
Instrukcje dotyczące tworzenia projektu różnią się w zależności od używanej wersji programu Visual Studio. Upewnij się, że masz wybraną poprawną wersję dokumentacji powyżej spisu treści na tej stronie.
Aby utworzyć przykładowy projekt w programie Visual Studio
Na pasku menu wybierz pozycję Plik>nowy>projekt, aby otworzyć okno dialogowe Tworzenie nowego projektu.
W górnej części okna dialogowego ustaw wartość Language na C++, ustaw wartość Platforma na Windows, a następnie ustaw wartość Project type (Typ projektu) na Console (Konsola).
Z filtrowanej listy typów projektów wybierz pozycję Aplikacja konsolowa, a następnie wybierz pozycję Dalej. Na następnej stronie wprowadź
AMPMapReduce
w polu Nazwa , aby określić nazwę projektu i określić lokalizację projektu, jeśli chcesz użyć innej.Wybierz przycisk Utwórz, aby utworzyć projekt klienta.
Aby utworzyć przykładowy projekt w programie Visual Studio 2017 lub Visual Studio 2015
Uruchom program Visual Studio.
Na pasku menu wybierz pozycję Plik>nowy>projekt.
W obszarze Zainstalowane w okienku szablonów wybierz pozycję Visual C++.
Wybierz pozycję Aplikacja konsolowa Win32, wpisz
AMPMapReduce
w polu Nazwa, a następnie wybierz przycisk OK.Wybierz przycisk Dalej.
Wyczyść pole wyboru Prekompilowany nagłówek, a następnie wybierz przycisk Zakończ.
W Eksplorator rozwiązań usuń plik stdafx.h, targetver.h i stdafx.cpp z projektu.
Następny:
Otwórz AMPMapReduce.cpp i zastąp jego zawartość następującym kodem.
// 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; }
Na pasku menu wybierz pozycję Plik>Zapisz wszystko.
W Eksplorator rozwiązań otwórz menu skrótów ampMapReduce, a następnie wybierz pozycję Właściwości.
W oknie dialogowym Strony właściwości w obszarze Właściwości konfiguracji wybierz pozycję Prekompilowane nagłówki C/C++>.
Dla właściwości Prekompiled Header (Prekompilowany nagłówek) wybierz pozycję Not Using Precompiled Headers (Nieużywane prekompilowane nagłówki), a następnie wybierz przycisk OK.
Na pasku menu wybierz pozycję Kompiluj rozwiązanie kompilacji>.
Debugowanie kodu procesora CPU
W tej procedurze użyjesz lokalnego debugera systemu Windows, aby upewnić się, że kod procesora CPU w tej aplikacji jest poprawny. Segment kodu procesora CPU w tej aplikacji, który jest szczególnie interesujący, to pętla for
reduction_sum_gpu_kernel
w funkcji. Steruje redukcją równoległą opartą na drzewie, która jest uruchamiana na procesorze GPU.
Aby debugować kod procesora CPU
W Eksplorator rozwiązań otwórz menu skrótów ampMapReduce, a następnie wybierz pozycję Właściwości.
W oknie dialogowym Strony właściwości w obszarze Właściwości konfiguracji wybierz pozycję Debugowanie. Sprawdź, czy w debugerze jest zaznaczona opcja Lokalny debuger systemu Windows, aby uruchomić listę.
Wróć do Edytora kodu.
Ustaw punkty przerwania w wierszach kodu pokazanych na poniższej ilustracji (około wiersze 67 wierszy 70).
Punkty przerwania procesora CPUNa pasku menu wybierz pozycję Debuguj>Rozpocznij debugowanie.
W oknie Ustawienia lokalne zwróć uwagę na wartość do
stride_size
momentu osiągnięcia punktu przerwania w wierszu 70.Na pasku menu wybierz pozycję Debuguj Zatrzymaj>debugowanie debugowania.
Debugowanie kodu procesora GPU
W tej sekcji pokazano, jak debugować kod procesora GPU, który jest kodem zawartym w sum_kernel_tiled
funkcji. Kod procesora GPU oblicza sumę liczb całkowitych dla każdego "bloku" równolegle.
Aby debugować kod procesora GPU
W Eksplorator rozwiązań otwórz menu skrótów ampMapReduce, a następnie wybierz pozycję Właściwości.
W oknie dialogowym Strony właściwości w obszarze Właściwości konfiguracji wybierz pozycję Debugowanie.
Na liście Debuger do uruchomienia wybierz pozycję Lokalny debuger systemu Windows.
Na liście Typ debugera sprawdź, czy wybrano opcję Auto.
Auto jest wartością domyślną. W wersjach wcześniejszych niż Windows 10 tylko procesor GPU jest wymagana wartość zamiast Auto.
Wybierz przycisk OK.
Ustaw punkt przerwania w wierszu 30, jak pokazano na poniższej ilustracji.
Punkt przerwania procesora GPUNa pasku menu wybierz pozycję Debuguj>Rozpocznij debugowanie. Punkty przerwania w kodzie procesora CPU w wierszach 67 i 70 nie są wykonywane podczas debugowania procesora GPU, ponieważ te wiersze kodu działają na procesorze CPU.
Aby użyć okna Wątki procesora GPU
Aby otworzyć okno Wątki procesora GPU, na pasku menu wybierz pozycję Debuguj>wątki procesora GPU systemu Windows.>
Możesz sprawdzić stan wątków procesora GPU w wyświetlonym oknie Wątki procesora GPU.
Zadokuj okno Wątki procesora GPU w dolnej części programu Visual Studio. Wybierz przycisk Rozwiń przełącznik wątku, aby wyświetlić pola tekstowe kafelka i wątku. W oknie Wątki procesora GPU jest wyświetlana łączna liczba aktywnych i zablokowanych wątków procesora GPU, jak pokazano na poniższej ilustracji.
Okno Wątki procesora GPU313 kafelków zostanie przydzielonych do tego obliczenia. Każdy kafelek zawiera 32 wątki. Ponieważ lokalne debugowanie procesora GPU odbywa się w emulatorze oprogramowania, istnieją cztery aktywne wątki procesora GPU. Cztery wątki wykonują instrukcje jednocześnie, a następnie przechodzą razem do następnej instrukcji.
W oknie Wątki procesora GPU istnieją cztery wątki procesora GPU aktywne i 28 wątków procesora GPU zablokowane w tile_barrier::wait instrukcji zdefiniowanej w wierszu około 21 (
t_idx.barrier.wait();
). Wszystkie wątki procesora GPU 32 należą do pierwszego kafelka.tile[0]
Strzałka wskazuje wiersz zawierający bieżący wątek. Aby przełączyć się na inny wątek, użyj jednej z następujących metod:W wierszu wątku, na który ma przejść w oknie Wątki procesora GPU, otwórz menu skrótów i wybierz pozycję Przełącz do wątku. Jeśli wiersz reprezentuje więcej niż jeden wątek, przełączysz się do pierwszego wątku zgodnie ze współrzędnymi wątku.
Wprowadź wartości kafelka i wątku wątku w odpowiednich polach tekstowych, a następnie wybierz przycisk Przełącz wątek .
W oknie Stos wywołań zostanie wyświetlony stos wywołań bieżącego wątku procesora GPU.
Aby użyć okna stosów równoległych
Aby otworzyć okno Stosy równoległe, na pasku menu wybierz pozycję Debuguj>stosy równoległe systemu Windows.>
Możesz użyć okna Stosy równoległe , aby jednocześnie sprawdzić ramki stosu wielu wątków procesora GPU.
Zadokuj okno Stosy równoległe w dolnej części programu Visual Studio.
Upewnij się, że na liście w lewym górnym rogu wybrano pozycję Wątki . Na poniższej ilustracji w oknie Stosy równoległe przedstawiono widok skoncentrowany na stosie wywołań wątków procesora GPU, które można zobaczyć w oknie Wątki procesora GPU.
Okno Stosy równoległe32 wątki przeszły od
_kernel_stub
do instrukcji lambda wparallel_for_each
wywołaniu funkcji, a następnie dosum_kernel_tiled
funkcji, gdzie występuje redukcja równoległa. 28 z 32 wątków postępuje dotile_barrier::wait
instrukcji i pozostają zablokowane w wierszu 22, podczas gdy pozostałe cztery wątki pozostają aktywne w funkcji wsum_kernel_tiled
wierszu 30.Możesz sprawdzić właściwości wątku procesora GPU. Są one dostępne w oknie Wątki procesora GPU w rozbudowanej etykietce danych okna stosów równoległych. Aby je zobaczyć, umieść wskaźnik na ramce stosu .
sum_kernel_tiled
Na poniższej ilustracji przedstawiono etykietkę danych.
Etykietka danych wątku procesora GPUAby uzyskać więcej informacji na temat okna stosów równoległych, zobacz Using the Parallel Stacks Window (Korzystanie z okna stosów równoległych).
Aby użyć okna Zegarek równoległy
Aby otworzyć okno Równoległy zegarek, na pasku menu wybierz pozycję Debuguj>równoległy zegarek z systemem Windows>Parallel Watch>1.
Możesz użyć okna Obserwator równoległy, aby sprawdzić wartości wyrażenia w wielu wątkach.
Zadokuj okno Parallel Watch 1 do dołu programu Visual Studio. W tabeli okna Równoległe obserwowanie znajduje się 32 wiersze. Każdy odpowiada wątkowi procesora GPU, który pojawił się zarówno w oknie Wątki procesora GPU, jak i w oknie Stosy równoległe . Teraz możesz wprowadzić wyrażenia, których wartości chcesz sprawdzić we wszystkich 32 wątkach procesora GPU.
Wybierz nagłówek kolumny Dodaj zegarek, wprowadź ciąg
localIdx
, a następnie wybierz Enter.Ponownie wybierz nagłówek kolumny Dodaj zegarek, wpisz
globalIdx
, a następnie wybierz Enter.Ponownie wybierz nagłówek kolumny Dodaj zegarek, wpisz
localA[localIdx[0]]
, a następnie wybierz Enter.Można sortować według określonego wyrażenia, wybierając odpowiedni nagłówek kolumny.
Wybierz nagłówek kolumny localA[localIdx[0]], aby posortować kolumnę. Poniższa ilustracja przedstawia wyniki sortowania według localA[localIdx[0]].
Wyniki sortowaniaZawartość można wyeksportować w oknie Parallel Watch do programu Excel, wybierając przycisk programu Excel , a następnie wybierając pozycję Otwórz w programie Excel. Jeśli program Excel jest zainstalowany na komputerze dewelopera, przycisk otwiera arkusz programu Excel zawierający zawartość.
W prawym górnym rogu okna Obserwator równoległy znajduje się kontrolka filtru, której można użyć do filtrowania zawartości przy użyciu wyrażeń logicznych. Wprowadź
localA[localIdx[0]] > 20000
w polu tekstowym kontrolki filtru, a następnie wybierz Enter .Okno zawiera teraz tylko wątki, na których
localA[localIdx[0]]
wartość jest większa niż 20000. Zawartość jest nadal sortowana wedługlocalA[localIdx[0]]
kolumny, czyli wybranej wcześniej akcji sortowania.
Flagowanie wątków procesora GPU
Określone wątki procesora GPU można oznaczyć, flagując je w oknie Wątki procesora GPU, w oknie Równoległy zegarek lub Etykietka danych w oknie Stosy równoległe . Jeśli wiersz w oknie Wątki procesora GPU zawiera więcej niż jeden wątek, flagując ten wiersz flaguje wszystkie wątki zawarte w wierszu.
Aby flagować wątki procesora GPU
Wybierz nagłówek kolumny [Thread] w oknie Parallel Watch 1, aby sortować według indeksu kafelka i indeksu wątku.
Na pasku menu wybierz pozycję Debuguj>kontynuuj, co powoduje, że cztery aktywne wątki przechodzą do następnej bariery (zdefiniowanej w wierszu 32 AMPMapReduce.cpp).
Wybierz symbol flagi po lewej stronie wiersza, który zawiera cztery wątki, które są teraz aktywne.
Na poniższej ilustracji przedstawiono cztery aktywne wątki oflagowane w oknie Wątki procesora GPU.
Aktywne wątki w oknie Wątki procesora GPUOkno Zegarek równoległy i etykietka danych okna Stosy równoległe wskazują oflagowane wątki.
Jeśli chcesz skupić się na czterech oflagowanych wątkach, możesz wybrać wyświetlanie tylko oflagowanych wątków. Ogranicza to, co widzisz w oknach Wątki procesora GPU, Zegarek równoległy i Stosy równoległe .
Wybierz przycisk Pokaż tylko oflagowany na dowolnym z okien lub na pasku narzędzi Lokalizacja debugowania. Poniższa ilustracja przedstawia przycisk Pokaż tylko oflagowany na pasku narzędzi Lokalizacja debugowania.
Pokaż przycisk Pokaż tylko oflagowaneTeraz okna Wątki procesora GPU, Zegarek równoległy i Równoległe stosy wyświetlają tylko oflagowane wątki.
Zamrażanie i odmrażanie wątków procesora GPU
Możesz zablokować (wstrzymać) i rozmrażyć (wznowić) wątki procesora GPU z okna wątków procesora GPU lub okna równoległego zegarka . Można zablokować i rozmrażyć wątki procesora w taki sam sposób; aby uzyskać informacje, zobacz Instrukcje: korzystanie z okna Wątki.
Aby zablokować i rozmrozić wątki procesora GPU
Wybierz przycisk Pokaż tylko oflagowany, aby wyświetlić wszystkie wątki.
Na pasku menu wybierz pozycję Debuguj>kontynuuj.
Otwórz menu skrótów dla aktywnego wiersza, a następnie wybierz pozycję Blokuj.
Poniższa ilustracja okna Wątki procesora GPU pokazuje, że wszystkie cztery wątki są zamrożone.
Zamrożone wątki w oknie Wątki procesora GPUPodobnie w oknie Parallel Watch widać, że wszystkie cztery wątki są zamrożone.
Na pasku menu wybierz pozycję Debuguj>kontynuuj, aby zezwolić następnym czterem wątkom procesora GPU na przechodzenie przez barierę w wierszu 22 i dotarcie do punktu przerwania w wierszu 30. Okno Wątki procesora GPU pokazuje, że cztery wcześniej zamrożone wątki pozostają zamrożone i w stanie aktywnym.
Na pasku menu wybierz pozycję Debuguj, Kontynuuj.
W oknie Zegarek równoległy można również rozmrozić poszczególne lub wiele wątków procesora GPU.
Aby zgrupować wątki procesora GPU
W menu skrótów dla jednego z wątków w oknie Wątki procesora GPU wybierz pozycję Grupuj według, Adres.
Wątki w oknie Wątki procesora GPU są pogrupowane według adresu. Adres odpowiada instrukcji w dezasemblacji, w której znajduje się każda grupa wątków. 24 wątki znajdują się w wierszu 22, gdzie jest wykonywana metoda tile_barrier::wait. 12 wątków znajdują się w instrukcji dla bariery w wierszu 32. Cztery z tych wątków są oflagowane. Osiem wątków znajdują się w punkcie przerwania w wierszu 30. Cztery z tych wątków są zamrożone. Na poniższej ilustracji przedstawiono pogrupowane wątki w oknie Wątki procesora GPU.
Pogrupowane wątki w oknie Wątki procesora GPUMożesz również wykonać operację Grupuj według, otwierając menu skrótów dla siatki danych okna równoległego zegarka. Wybierz pozycję Grupuj według, a następnie wybierz element menu odpowiadający sposobom grupowania wątków.
Uruchamianie wszystkich wątków do określonej lokalizacji w kodzie
Wszystkie wątki w danym kafelku są uruchamiane w wierszu zawierającym kursor za pomocą polecenia Uruchom bieżący kafelek do kursora.
Aby uruchomić wszystkie wątki do lokalizacji oznaczonej kursorem
W menu skrótów dla zamrożonych wątków wybierz pozycję Thaw.
W Edytorze kodu umieść kursor w wierszu 30.
W menu skrótów edytora kodu wybierz pozycję Uruchom bieżący kafelek do kursora.
24 wątki, które zostały wcześniej zablokowane w barierze na linii 21, postępują do linii 32. Jest on wyświetlany w oknie Wątki procesora GPU.
Zobacz też
Omówienie C++ AMP
Debugowanie kodu procesora GPU
Instrukcje: korzystanie z okna wątków GPU
Instrukcje: korzystanie z okna równoległego wyrażenia kontrolnego
Analizowanie kodu C++ AMP za pomocą wizualizatora współbieżności