Condividi tramite


Procedura dettagliata: Debug di un'applicazione C++ AMP

Questo articolo illustra come eseguire il debug di un'applicazione che usa C++ Accelerated Massive Parallelism (C++ AMP) per sfruttare i vantaggi dell'unità di elaborazione grafica (GPU). Usa un programma di riduzione parallela che somma una matrice di numeri interi di grandi dimensioni. In questa procedura dettagliata sono illustrati i task seguenti:

  • Avvio del debugger GPU.
  • Controllo dei thread GPU nella finestra Thread GPU.
  • Uso della finestra Stack paralleli per osservare contemporaneamente gli stack di chiamate di più thread GPU.
  • Uso della finestra Espressione di controllo parallelo per controllare i valori di una singola espressione tra più thread contemporaneamente.
  • Contrassegno, blocco, scongelamento e raggruppamento di thread GPU.
  • Esecuzione di tutti i thread di un riquadro in una posizione specifica nel codice.

Prerequisiti

Prima di iniziare questa procedura dettagliata:

Nota

Le intestazioni C++ AMP sono deprecate a partire da Visual Studio 2022 versione 17.0. L'inclusione di eventuali intestazioni AMP genererà errori di compilazione. Definire _SILENCE_AMP_DEPRECATION_WARNINGS prima di includere eventuali intestazioni AMP per disattivare gli avvisi.

Nota

I nomi o i percorsi visualizzati per alcuni elementi dell'interfaccia utente di Visual Studio nelle istruzioni seguenti potrebbero essere diversi nel computer in uso. La versione di Visual Studio in uso e le impostazioni configurate determinano questi elementi. Per altre informazioni, vedere Personalizzazione dell'IDE.

Per creare il progetto di esempio

Le istruzioni per la creazione di un progetto variano a seconda della versione di Visual Studio in uso. Assicurarsi di avere selezionato la versione corretta della documentazione sopra il sommario in questa pagina.

Per creare il progetto di esempio in Visual Studio

  1. Sulla barra dei menu scegliere File>Nuovo>Progetto per aprire la finestra di dialogo Crea nuovo progetto.

  2. Nella parte superiore della finestra di dialogo impostare Linguaggio su C++ , impostare Piattaforma su Windows e impostare Tipo di progetto su Console.

  3. Nell'elenco filtrato dei tipi di progetto scegliere App console e quindi scegliere Avanti. Nella pagina successiva immettere AMPMapReduce nella casella Nome per specificare un nome per il progetto e specificare il percorso del progetto se ne vuole uno diverso.

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

  4. Scegliere il pulsante Crea per creare il progetto client.

Per creare il progetto di esempio in Visual Studio 2017 o Visual Studio 2015

  1. Avviare Visual Studio.

  2. Nella barra dei menu scegliere File>Nuovo>Progetto.

  3. In Installato nel riquadro modelli scegliere Visual C++.

  4. Scegliere Applicazione console Win32, digitare AMPMapReduce nella casella Nome e quindi scegliere il pulsante OK .

  5. Fare clic su Avanti.

  6. Deselezionare la casella di controllo Intestazione precompilata e quindi scegliere il pulsante Fine .

  7. In Esplora soluzioni eliminare stdafx.h, targetver.h e stdafx.cpp dal progetto.

Successivo:

  1. Aprire AMPMapReduce.cpp e sostituirlo con il codice seguente.

    // 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. Sulla barra dei menu scegliere File>Salva tutto.

  3. In Esplora soluzioni aprire il menu di scelta rapida per AMPMapReduce e quindi scegliere Proprietà.

  4. Nella finestra di dialogo Pagine delle proprietà, in Proprietà di configurazione, scegliere Intestazioni precompilate C/C++>.

  5. Per la proprietà Intestazione precompilata, selezionare Not Using Precompiled Headers (Non utilizzare intestazioni precompilate) e quindi scegliere il pulsante OK.

  6. Nella barra dei menu scegliere Compila>Compila soluzione.

Debug del codice della CPU

In questa procedura si userà il debugger Windows locale per assicurarsi che il codice della CPU in questa applicazione sia corretto. Il segmento del codice della CPU in questa applicazione particolarmente interessante è il for ciclo nella reduction_sum_gpu_kernel funzione. Controlla la riduzione parallela basata su albero eseguita sulla GPU.

Per eseguire il debug del codice della CPU

  1. In Esplora soluzioni aprire il menu di scelta rapida per AMPMapReduce e quindi scegliere Proprietà.

  2. Nella finestra di dialogo Pagine delle proprietà scegliere Debug in Proprietà di configurazione. Verificare che l'opzione Debugger Windows locale sia selezionata nell'elenco Debugger per l'avvio.

  3. Tornare all'editor di codice.

  4. Impostare punti di interruzione sulle righe di codice illustrate nella figura seguente (circa le righe 67 riga 70).

    CPU breakpoints marked next to lines of code in the editor.
    Punti di interruzione CPU

  5. Sulla barra dei menu scegliere Debug>Avvia debug.

  6. Nella finestra Variabili locali osservare il valore per stride_size finché non viene raggiunto il punto di interruzione alla riga 70.

  7. Sulla barra dei menu scegliere Debug>Termina debug.

Debug del codice GPU

Questa sezione illustra come eseguire il debug del codice GPU, ovvero il codice contenuto nella sum_kernel_tiled funzione. Il codice GPU calcola la somma di interi per ogni "blocco" in parallelo.

Per eseguire il debug del codice GPU

  1. In Esplora soluzioni aprire il menu di scelta rapida per AMPMapReduce e quindi scegliere Proprietà.

  2. Nella finestra di dialogo Pagine delle proprietà scegliere Debug in Proprietà di configurazione.

  3. Nell'elenco Debugger da avviare selezionare Debugger Windows locale.

  4. Nell'elenco Tipo debugger verificare che l'opzione Auto sia selezionata.

    Auto è il valore predefinito. Nelle versioni precedenti a Windows 10, solo GPU è il valore richiesto anziché Auto.

  5. Scegliere il pulsante OK.

  6. Impostare un punto di interruzione alla riga 30, come illustrato nella figura seguente.

    GPU breakpoints marked next to a line of code in the editor.
    Punto di interruzione GPU

  7. Sulla barra dei menu scegliere Debug>Avvia debug. I punti di interruzione nel codice della CPU alle righe 67 e 70 non vengono eseguiti durante il debug GPU perché tali righe di codice vengono eseguite sulla CPU.

Per usare la finestra Thread GPU

  1. Per aprire la finestra Thread GPU, nella barra dei menu scegliere Debug>thread GPU di Windows.>

    È possibile esaminare lo stato dei thread GPU nella finestra Thread GPU visualizzata.

  2. Ancorare la finestra Thread GPU nella parte inferiore di Visual Studio. Scegliere il pulsante Espandi commutatore thread per visualizzare le caselle di testo riquadro e thread. La finestra Thread GPU mostra il numero totale di thread GPU attivi e bloccati, come illustrato nella figura seguente.

    GPU Threads window with 4 active threads.
    Finestra Thread GPU

    313 riquadri vengono allocati per questo calcolo. Ogni riquadro contiene 32 thread. Poiché il debug GPU locale si verifica in un emulatore software, sono presenti quattro thread GPU attivi. I quattro thread eseguono le istruzioni contemporaneamente e quindi passano insieme all'istruzione successiva.

    Nella finestra Thread GPU sono presenti quattro thread GPU attivi e 28 thread GPU bloccati nell'istruzione tile_barrier::wait definita alla riga 21 (t_idx.barrier.wait();). Tutti i 32 thread GPU appartengono al primo riquadro, tile[0]. Una freccia punta alla riga che include il thread corrente. Per passare a un thread diverso, usare uno dei metodi seguenti:

    • Nella riga a cui passare il thread nella finestra Thread GPU aprire il menu di scelta rapida e scegliere Passa al thread. Se la riga rappresenta più thread, si passerà al primo thread in base alle coordinate del thread.

    • Immettere i valori di riquadro e thread del thread nelle caselle di testo corrispondenti e quindi scegliere il pulsante Cambia thread .

    Nella finestra Stack di chiamate viene visualizzato lo stack di chiamate del thread GPU corrente.

Per usare la finestra Stack paralleli

  1. Per aprire la finestra Stack paralleli, nella barra dei menu scegliere Debug>stack paralleli di Windows.>

    È possibile usare la finestra Stack paralleli per esaminare contemporaneamente gli stack frame di più thread GPU.

  2. Ancorare la finestra Stack paralleli nella parte inferiore di Visual Studio.

  3. Assicurarsi che l'opzione Thread sia selezionata nell'elenco nell'angolo superiore sinistro. Nella figura seguente la finestra Stack paralleli mostra una visualizzazione incentrata sullo stack di chiamate dei thread GPU visualizzati nella finestra Thread GPU.

    Parallel Stacks window with 4 active threads.
    Finestra Stack in parallelo

    32 thread passano dall'istruzione _kernel_stub lambda nella parallel_for_each chiamata di funzione e quindi alla sum_kernel_tiled funzione, in cui si verifica la riduzione parallela. 28 thread su 32 sono stati spostati nell'istruzione tile_barrier::wait e rimangono bloccati alla riga 22, mentre gli altri quattro thread rimangono attivi nella funzione alla sum_kernel_tiled riga 30.

    È possibile esaminare le proprietà di un thread GPU. Sono disponibili nella finestra Thread GPU nella descrizione dati avanzata della finestra Stack paralleli . Per visualizzarli, posizionare il puntatore sul frame dello stack di sum_kernel_tiled. Nella figura seguente viene illustrato il suggerimento dati.

    DataTip for Parallel Stacks window.
    Descrizione dati thread GPU

    Per altre informazioni sulla finestra Stack paralleli , vedere Uso della finestra Stack paralleli.

Per usare la finestra Espressione di controllo parallelo

  1. Per aprire la finestra Espressione di controllo parallelo, nella barra dei menu scegliere Debug>Di Windows>Parallel Watch Parallel Watch>1.

    È possibile usare la finestra Espressione di controllo parallelo per esaminare i valori di un'espressione in più thread.

  2. Ancorare la finestra Espressione di controllo parallelo 1 alla fine di Visual Studio. Nella tabella della finestra Espressione di controllo parallelo sono presenti 32 righe. Ognuno corrisponde a un thread GPU visualizzato sia nella finestra Thread GPU che nella finestra Stack paralleli . È ora possibile immettere espressioni i cui valori si desidera esaminare in tutti i 32 thread GPU.

  3. Selezionare l'intestazione Aggiungi colonna Espressione di controllo , immettere localIdxe quindi premere INVIO .

  4. Selezionare di nuovo l'intestazione Aggiungi colonna espressione di controllo, digitare globalIdxe quindi scegliere invio.

  5. Selezionare di nuovo l'intestazione Aggiungi colonna espressione di controllo, digitare localA[localIdx[0]]e quindi scegliere invio.

    È possibile ordinare in base a un'espressione specificata selezionando l'intestazione di colonna corrispondente.

    Selezionare l'intestazione di colonna localA[localIdx[0]] per ordinare la colonna. La figura seguente mostra i risultati dell'ordinamento in base a localA[localIdx[0]].

    Parallel Watch window with sorted results.
    Risultati ordinamento

    È possibile esportare il contenuto nella finestra Espressione di controllo parallelo in Excel scegliendo il pulsante Excel e quindi scegliendo Apri in Excel. Se Excel è installato nel computer di sviluppo, il pulsante apre un foglio di lavoro di Excel che contiene il contenuto.

  6. Nell'angolo superiore destro della finestra Espressione di controllo parallelo è presente un controllo filtro che è possibile usare per filtrare il contenuto usando espressioni booleane. Immettere localA[localIdx[0]] > 20000 nella casella di testo del controllo filtro e quindi scegliere invio.

    La finestra contiene ora solo thread su cui il localA[localIdx[0]] valore è maggiore di 20000. Il contenuto è ancora ordinato in base alla localA[localIdx[0]] colonna, ovvero l'azione di ordinamento scelta in precedenza.

Contrassegno dei thread GPU

È possibile contrassegnare thread GPU specifici contrassegnandoli nella finestra Thread GPU, nella finestra Espressione di controllo parallelo o nella descrizione dati nella finestra Stack paralleli. Se una riga nella finestra Thread GPU contiene più di un thread, contrassegnando la riga che contrassegna tutti i thread contenuti nella riga.

Per contrassegnare i thread GPU

  1. Selezionare l'intestazione di colonna [Thread] nella finestra Espressione di controllo parallelo 1 per ordinare in base all'indice del riquadro e all'indice del thread.

  2. Sulla barra dei menu scegliere Debug>continua, che fa sì che i quattro thread attivi passino alla barriera successiva (definita alla riga 32 di AMPMapReduce.cpp).

  3. Scegliere il simbolo del flag sul lato sinistro della riga che contiene i quattro thread che sono ora attivi.

    La figura seguente mostra i quattro thread contrassegnati attivi nella finestra Thread GPU.

    GPU Threads window with flagged threads.
    Thread attivi nella finestra Thread GPU

    La finestra Espressione di controllo parallelo e la descrizione dati della finestra Stack paralleli indicano entrambi i thread contrassegnati .

  4. Se si desidera concentrarsi sui quattro thread contrassegnati, è possibile scegliere di visualizzare solo i thread contrassegnati. Limita gli elementi visualizzati nelle finestre Thread GPU, Espressione di controllo parallelo e Stack paralleli.

    Scegliere il pulsante Mostra solo contrassegnato in una delle finestre o sulla barra degli strumenti Percorso di debug. La figura seguente mostra il pulsante Mostra solo flag sulla barra degli strumenti Percorso di debug.

    Debug Location toolbar with Show Only Flagged icon.
    Pulsante Mostra solo flag

    Ora le finestre Thread GPU, Espressione di controllo parallelo e Stack paralleli visualizzano solo i thread contrassegnati.

Blocco e scosatura di thread GPU

È possibile bloccare (sospendere) e sbloccare (riprendere) i thread GPU dalla finestra Thread GPU o dalla finestra Espressione di controllo parallelo. È possibile bloccare e scongelare i thread della CPU nello stesso modo; per informazioni, vedere Procedura: Usare la finestra Thread.

Per bloccare e sbloccare i thread GPU

  1. Scegliere il pulsante Mostra solo flag per visualizzare tutti i thread.

  2. Sulla barra dei menu scegliere Debug>Continua.

  3. Aprire il menu di scelta rapida per la riga attiva e quindi scegliere Blocca.

    La figura seguente della finestra Thread GPU mostra che tutti e quattro i thread sono bloccati.

    GPU Threads windows showing frozen threads.
    Thread bloccati nella finestra Thread GPU

    Analogamente, la finestra Espressione di controllo parallelo mostra che tutti e quattro i thread sono bloccati.

  4. Sulla barra dei menu scegliere Debug>Continua per consentire ai quattro thread GPU successivi di andare oltre la barriera alla riga 22 e di raggiungere il punto di interruzione alla riga 30. La finestra Thread GPU mostra che i quattro thread bloccati in precedenza rimangono bloccati e nello stato attivo.

  5. Sulla barra dei menu scegliere Debug, Continua.

  6. Dalla finestra Espressione di controllo parallelo è anche possibile scongelare singoli o più thread GPU.

Per raggruppare i thread GPU

  1. Nel menu di scelta rapida per uno dei thread nella finestra Thread GPU scegliere Raggruppa per, Indirizzo.

    I thread nella finestra Thread GPU vengono raggruppati in base all'indirizzo. L'indirizzo corrisponde all'istruzione in disassembly in cui si trova ogni gruppo di thread. 24 thread si trovano alla riga 22 in cui viene eseguito il metodo tile_barrier::wait. 12 thread sono all'istruzione per la barriera alla riga 32. Quattro di questi thread vengono contrassegnati. Otto thread si trovano nel punto di interruzione alla riga 30. Quattro di questi thread sono congelati. La figura seguente mostra i thread raggruppati nella finestra Thread GPU.

    GPU Threads window with threads grouped by Address.
    Thread raggruppati nella finestra Thread GPU

  2. È anche possibile eseguire l'operazione Raggruppa per aprendo il menu di scelta rapida per la griglia dei dati della finestra Espressione di controllo parallelo. Selezionare Raggruppa per e quindi scegliere la voce di menu corrispondente alla modalità di raggruppamento dei thread.

Esecuzione di tutti i thread in una posizione specifica nel codice

Tutti i thread in un determinato riquadro vengono eseguiti sulla riga contenente il cursore usando Esegui riquadro corrente al cursore.

Per eseguire tutti i thread nella posizione contrassegnata dal cursore

  1. Scegliere Thaw dal menu di scelta rapida per i thread bloccati.

  2. Nell'editor di codice inserire il cursore nella riga 30.

  3. Nel menu di scelta rapida dell'editor di codice scegliere Esegui riquadro corrente in cursore.

    I 24 thread precedentemente bloccati alla barriera alla linea 21 sono passati alla riga 32. Viene visualizzato nella finestra Thread GPU.

Vedi anche

Panoramica di C++ AMP
Debug del codice GPU
Procedura: Usare la finestra Thread GPU
Procedura: Usare la finestra Espressione di controllo in parallelo
Analisi del codice AMP C++ con il visualizzatore di concorrenza