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.

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

  1. Na pasku menu wybierz pozycję Plik>nowy>projekt, aby otworzyć okno dialogowe Tworzenie nowego projektu.

  2. 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).

  3. 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.

    Screenshot showing the Create a new project dialog with the Console App template selected.

  4. Wybierz przycisk Utwórz, aby utworzyć projekt klienta.

Aby utworzyć przykładowy projekt w programie Visual Studio 2017 lub Visual Studio 2015

  1. Uruchom program Visual Studio.

  2. Na pasku menu wybierz pozycję Plik>nowy>projekt.

  3. W obszarze Zainstalowane w okienku szablonów wybierz pozycję Visual C++.

  4. Wybierz pozycję Aplikacja konsolowa Win32, wpisz AMPMapReduce w polu Nazwa, a następnie wybierz przycisk OK.

  5. Wybierz przycisk Dalej.

  6. Wyczyść pole wyboru Prekompilowany nagłówek, a następnie wybierz przycisk Zakończ.

  7. W Eksplorator rozwiązań usuń plik stdafx.h, targetver.h i stdafx.cpp z projektu.

Dalej:

  1. Otwórz plik 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;
    }
    
  2. Na pasku menu wybierz pozycję Plik>Zapisz wszystko.

  3. W Eksplorator rozwiązań otwórz menu skrótów ampMapReduce, a następnie wybierz pozycję Właściwości.

  4. W oknie dialogowym Strony właściwości w obszarze Właściwości konfiguracji wybierz pozycję Prekompilowane nagłówki C/C++>.

  5. 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.

  6. 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 forreduction_sum_gpu_kernel w funkcji. Steruje redukcją równoległą opartą na drzewie, która jest uruchamiana na procesorze GPU.

Aby debugować kod procesora CPU

  1. W Eksplorator rozwiązań otwórz menu skrótów ampMapReduce, a następnie wybierz pozycję Właściwości.

  2. 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ę.

  3. Wróć do Edytora kodu.

  4. Ustaw punkty przerwania w wierszach kodu pokazanych na poniższej ilustracji (około wiersze 67 wierszy 70).

    CPU breakpoints marked next to lines of code in the editor.
    Punkty przerwania procesora CPU

  5. Na pasku menu wybierz pozycję Debuguj>Rozpocznij debugowanie.

  6. W oknie Ustawienia lokalne zwróć uwagę na wartość do stride_size momentu osiągnięcia punktu przerwania w wierszu 70.

  7. 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

  1. W Eksplorator rozwiązań otwórz menu skrótów ampMapReduce, a następnie wybierz pozycję Właściwości.

  2. W oknie dialogowym Strony właściwości w obszarze Właściwości konfiguracji wybierz pozycję Debugowanie.

  3. Na liście Debuger do uruchomienia wybierz pozycję Lokalny debuger systemu Windows.

  4. 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.

  5. Wybierz przycisk OK.

  6. Ustaw punkt przerwania w wierszu 30, jak pokazano na poniższej ilustracji.

    GPU breakpoints marked next to a line of code in the editor.
    Punkt przerwania procesora GPU

  7. Na 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

  1. 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.

  2. 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.

    GPU Threads window with 4 active threads.
    Okno Wątki procesora GPU

    313 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

  1. 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.

  2. Zadokuj okno Stosy równoległe w dolnej części programu Visual Studio.

  3. 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.

    Parallel Stacks window with 4 active threads.
    Okno Stosy równoległe

    32 wątki przeszły od _kernel_stub do instrukcji lambda w parallel_for_each wywołaniu funkcji, a następnie do sum_kernel_tiled funkcji, gdzie występuje redukcja równoległa. 28 z 32 wątków postępuje do tile_barrier::wait instrukcji i pozostają zablokowane w wierszu 22, podczas gdy pozostałe cztery wątki pozostają aktywne w funkcji w sum_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.

    DataTip for Parallel Stacks window.
    Etykietka danych wątku procesora GPU

    Aby 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

  1. 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.

  2. 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.

  3. Wybierz nagłówek kolumny Dodaj zegarek, wprowadź ciąg localIdx, a następnie wybierz klawisz Enter.

  4. Ponownie wybierz nagłówek kolumny Dodaj zegarek, wpisz globalIdx, a następnie wybierz klawisz Enter.

  5. Ponownie wybierz nagłówek kolumny Dodaj zegarek, wpisz localA[localIdx[0]], a następnie wybierz klawisz 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]].

    Parallel Watch window with sorted results.
    Wyniki sortowania

    Zawartość 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ść.

  6. 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 klawisz Enter .

    Okno zawiera teraz tylko wątki, na których localA[localIdx[0]] wartość jest większa niż 20000. Zawartość jest nadal sortowana według localA[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

  1. Wybierz nagłówek kolumny [Thread] w oknie Parallel Watch 1, aby sortować według indeksu kafelka i indeksu wątku.

  2. Na pasku menu wybierz pozycję Debuguj>kontynuuj, co powoduje, że cztery aktywne wątki przechodzą do następnej bariery (zdefiniowane w wierszu 32 ampMapReduce.cpp).

  3. 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.

    GPU Threads window with flagged threads.
    Aktywne wątki w oknie Wątki procesora GPU

    Okno Zegarek równoległy i etykietka danych okna Stosy równoległe wskazują oflagowane wątki.

  4. 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.

    Debug Location toolbar with Show Only Flagged icon.
    Pokaż przycisk Pokaż tylko oflagowane

    Teraz 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

  1. Wybierz przycisk Pokaż tylko oflagowany, aby wyświetlić wszystkie wątki.

  2. Na pasku menu wybierz pozycję Debuguj>kontynuuj.

  3. 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.

    GPU Threads windows showing frozen threads.
    Zamrożone wątki w oknie Wątki procesora GPU

    Podobnie w oknie Parallel Watch widać, że wszystkie cztery wątki są zamrożone.

  4. 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.

  5. Na pasku menu wybierz pozycję Debuguj, Kontynuuj.

  6. 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

  1. 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.

    GPU Threads window with threads grouped by Address.
    Pogrupowane wątki w oknie Wątki procesora GPU

  2. Moż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

  1. W menu skrótów dla zamrożonych wątków wybierz pozycję Thaw.

  2. W Edytorze kodu umieść kursor w wierszu 30.

  3. 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