Freigeben über


Exemplarische Vorgehensweise: Debuggen einer C++ AMP-Anwendung

In diesem Artikel wird veranschaulicht, wie Sie eine Anwendung debuggen, die C++ Accelerated Massive Parallelism (C++ AMP) verwendet, um die Grafikverarbeitungseinheit (GPU) zu nutzen. Es verwendet ein Parallel-Reduction-Programm, das ein großes Array von ganzen Zahlen summiert. In dieser exemplarischen Vorgehensweise werden die folgenden Aufgaben veranschaulicht:

  • Starten des GPU-Debuggers.
  • Überprüfen von GPU-Threads im GPU-Threads-Fenster.
  • Verwenden des Fensters "Parallele Stapel" , um gleichzeitig die Aufrufstapel mehrerer GPU-Threads zu beobachten.
  • Verwenden des Fensters " Parallele Überwachung ", um Werte eines einzelnen Ausdrucks über mehrere Threads gleichzeitig zu prüfen.
  • Kennzeichnen, Einfrieren, Auftauen und Gruppieren von GPU-Threads
  • Ausführen sämtlicher Threads einer Kachel an eine spezifische Stelle im Code.

Voraussetzungen

Bevor Sie mit dieser Anleitung beginnen:

Hinweis

C++AMP-Header sind ab Visual Studio 2022, Version 17.0, veraltet. Wenn alle AMP-Header einbezogen werden, führt dies zu Buildfehlern. Definieren Sie _SILENCE_AMP_DEPRECATION_WARNINGS , bevor Sie AMP-Header einschließen, um die Warnungen zu stillen.

  • Read C++ AMP Overview.
  • Stellen Sie sicher, dass Zeilennummern im Text-Editor angezeigt werden. Weitere Informationen finden Sie unter Vorgehensweise: Zeilennummern im Editor anzeigen.
  • Stellen Sie sicher, dass Sie mindestens Windows 8 oder Windows Server 2012 ausführen, um das Debuggen im Softwareemulator zu unterstützen.

Hinweis

Auf Ihrem Computer werden möglicherweise andere Namen oder Speicherorte für die Benutzeroberflächenelemente von Visual Studio angezeigt als die in den folgenden Anweisungen aufgeführten. Diese Elemente sind von der jeweiligen Visual Studio-Version und den verwendeten Einstellungen abhängig. Weitere Informationen finden Sie unter Personalisieren der IDE.

So erstellen Sie das Beispielprojekt

Die Anweisungen zum Erstellen eines Projekts variieren je nachdem, welche Version von Visual Studio Sie verwenden. Stellen Sie sicher, dass die richtige Dokumentationsversion oberhalb des Inhaltsverzeichnisses auf dieser Seite ausgewählt ist.

So erstellen Sie das Beispielprojekt in Visual Studio

  1. Klicken Sie in der Menüleiste auf Datei>Neu>Projekt, um das Dialogfeld Neues Projekt erstellen zu öffnen.

  2. Legen Sie oben im Dialogfeld die Sprache auf C++, die Plattform auf Windows und den Projekttyp auf Konsole fest.

  3. Wählen Sie aus der gefilterten Projekttypliste Konsolen-App aus, und klicken Sie auf Weiter. Geben Sie auf der nächsten Seite im Feld Name einen Namen für das Projekt an und geben Sie den Projektspeicherort an, wenn Sie an einem anderen Speicherort speichern möchten.

    Screenshot des Dialogfelds

  4. Klicken Sie auf die Schaltfläche Erstellen, um das Clientprojekt zu erstellen.

So erstellen Sie das Beispielprojekt in Visual Studio 2017 oder Visual Studio 2015

  1. Starten Sie Visual Studio.

  2. Klicken Sie in der Menüleiste auf Datei>Neu>Projekt.

  3. Wählen Sie unter Installiert im Vorlagenbereich Visual C++ aus.

  4. Wählen Sie "Win32-Konsolenanwendung", geben Sie in das Name-Feld AMPMapReduce ein, und wählen Sie dann die Schaltfläche "OK" aus.

  5. Klicken Sie auf Weiter.

  6. Deaktivieren Sie das Kontrollkästchen "Vorkompilierte Kopfzeile ", und wählen Sie dann die Schaltfläche "Fertig stellen " aus.

  7. In Projektmappen-Explorer löschen Sie stdafx.h, targetver.h und stdafx.cpp aus dem Projekt.

Weiter:

  1. Öffnen Sie AMPMapReduce.cpp, und ersetzen Sie den Inhalt durch den folgenden Code.

    // 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. Klicken Sie in der Menüleiste auf Datei>Alle speichern.

  3. Öffnen Sie in Projektmappen-Explorer das Kontextmenü für AMPMapReduce, und wählen Sie dann "Eigenschaften" aus.

  4. Im Dialogfeld Eigenschaftenseiten, unter Konfigurationseigenschaften, wählen Sie C/C++>Vorkompilierte Kopfzeilen aus.

  5. Wählen Sie für die Eigenschaft "Vorkompilierte Kopfzeilen " die Option "Keine vorkompilierten Kopfzeilen" aus, und klicken Sie dann auf die Schaltfläche "OK ".

  6. Wählen Sie auf der Menüleiste Erstellen>Lösung erstellen aus.

Debuggen des CPU-Codes

In diesem Verfahren verwenden Sie den lokalen Windows-Debugger, um sicherzustellen, dass der CPU-Code in dieser Anwendung korrekt ist. Das Segment des CPU-Codes in dieser Anwendung, das besonders interessant ist, ist die for Schleife in der reduction_sum_gpu_kernel Funktion. Sie steuert die strukturbasierte parallele Reduzierung, die auf der GPU ausgeführt wird.

So debuggen Sie den CPU-Code

  1. Öffnen Sie in Projektmappen-Explorer das Kontextmenü für AMPMapReduce, und wählen Sie dann "Eigenschaften" aus.

  2. Wählen Sie im Dialogfeld "Eigenschaftenseiten" unter "Konfigurationseigenschaften" die Option "Debuggen" aus. Vergewissern Sie sich, dass der lokale Windows-Debugger in der Liste "Debugger zum Starten " ausgewählt ist.

  3. Kehren Sie zum Code-Editor zurück.

  4. Legen Sie Haltepunkte in den Codezeilen fest, die in der folgenden Abbildung dargestellt sind (etwa Zeilen 67 Zeile 70).

    CPU-Haltepunkte, die neben Codezeilen im Editor markiert sind.
    CPU-Haltepunkte

  5. Klicken Sie in der Menüleiste auf Debuggen>Debuggen starten.

  6. Beobachten Sie im "Lokale Variablen"-Fenster den Wert für stride_size bis der Haltepunkt in Zeile 70 erreicht ist.

  7. Klicken Sie in der Menüleiste auf Debuggen>Debuggen beenden aus.

Debuggen des GPU-Codes

In diesem Abschnitt wird gezeigt, wie Sie den GPU-Code debuggen, bei dem es sich um den code handelt, der in der sum_kernel_tiled Funktion enthalten ist. Der GPU-Code berechnet die Summe der ganzen Zahlen für jeden "Block" parallel.

So debuggen Sie den GPU-Code

  1. Öffnen Sie in Projektmappen-Explorer das Kontextmenü für AMPMapReduce, und wählen Sie dann "Eigenschaften" aus.

  2. Wählen Sie im Dialogfeld "Eigenschaftenseiten" unter "Konfigurationseigenschaften" die Option "Debuggen" aus.

  3. Wählen Sie in der Liste Zu startender Debugger die Option Lokaler Windows-Debugger aus.

  4. Überprüfen Sie in der Liste "Debuggertyp ", ob "Auto" ausgewählt ist.

    Auto ist der Standardwert. In Versionen vor Windows 10 ist GPU Only der erforderliche Wert, anstelle von Auto.

  5. Klicken Sie auf die Schaltfläche OK .

  6. Legen Sie einen Haltepunkt in Zeile 30 fest, wie in der folgenden Abbildung dargestellt.

    GPU-Haltepunkte, die neben einer Codezeile im Editor markiert sind.
    GPU-Haltepunkt

  7. Klicken Sie in der Menüleiste auf Debuggen>Debuggen starten. Die Haltepunkte im CPU-Code in den Zeilen 67 und 70 werden während des GPU-Debuggings nicht ausgeführt, da diese Codezeilen auf der CPU ausgeführt werden.

So verwenden Sie das GPU-Threads-Fenster

  1. Um das GPU-Threads-Fenster zu öffnen, wählen Sie auf der Menüleiste Debug>Windows>GPU-Threads aus.

    Sie können den Zustand der GPU-Threads im angezeigten GPU-Threads-Fenster überprüfen.

  2. Docken Sie das FENSTER "GPU-Threads " am unteren Rand von Visual Studio an. Wählen Sie die Schaltfläche 'Erweitern' der Threadanzeige aus, um die Kachel- und Threadtextfelder anzuzeigen. Das Fenster "GPU-Threads " zeigt die Gesamtanzahl der aktiven und blockierten GPU-Threads an, wie in der folgenden Abbildung dargestellt.

    GPU-Threads-Fenster mit 4 aktiven Threads.
    GPU-Thread-Fenster

    313 Kacheln werden für diese Berechnung zugewiesen. Jede Kachel enthält 32 Threads. Da das lokale GPU-Debugging in einem Softwareemulator auftritt, gibt es vier aktive GPU-Threads. Die vier Threads führen die Anweisungen gleichzeitig aus und fahren dann mit der nächsten Anweisung fort.

    Im Fenster "GPU-Threads" sind vier GPU-Threads aktiv und 28 GPU-Threads an der tile_barrier::wait-Anweisung blockiert, die in etwa zeile 21 (t_idx.barrier.wait();) definiert ist. Alle 32 GPU-Threads gehören zur ersten Kachel. tile[0] Ein Pfeil zeigt auf die Zeile, die den aktuellen Thread enthält. Verwenden Sie eine der folgenden Methoden, um zu einem anderen Thread zu wechseln:

    • Öffnen Sie in der Zeile für den Thread, zu dem im Fenster GPU Threads gewechselt werden soll, das Kontextmenü und wählen Sie Zu Thread wechseln aus. Wenn die Zeile mehr als einen Thread darstellt, wechseln Sie entsprechend den Threadkoordinaten zum ersten Thread.

    • Geben Sie die Kachel- und Threadwerte des Threads in die entsprechenden Textfelder ein, und wählen Sie dann die Schaltfläche "Thread wechseln" aus.

    Im Fenster "Aufrufstapel" wird der Aufrufstapel des aktuellen GPU-Threads angezeigt.

So verwenden Sie das Fenster "Parallele Stapel"

  1. Um das Fenster "Parallele Stapel" zu öffnen, wählen Sie in der Menüleiste Debug>Fenster>Parallele Stapel aus.

    Sie können das Fenster "Parallele Stapel" verwenden, um die Stapelframes mehrerer GPU-Threads gleichzeitig zu prüfen.

  2. Andocken Sie das Fenster "Parallele Stapel" am unteren Rand von Visual Studio.

  3. Stellen Sie sicher, dass Threads in der Liste in der oberen linken Ecke ausgewählt sind. In der folgenden Abbildung zeigt das Fenster "Parallele Stapel" eine aufrufstapelorientierte Ansicht der GPU-Threads, die Sie im Fenster "GPU-Threads " gesehen haben.

    Parallel stacks window with 4 active threads.
    Fenster Parallele Stapel

    32 Threads gingen von _kernel_stub zur Lambda-Anweisung im parallel_for_each Funktionsaufruf und dann zur sum_kernel_tiled Funktion, wo die parallele Reduzierung stattfindet. 28 der 32 Threads sind zur tile_barrier::wait Anweisung vorangekommen und bleiben in Zeile 22 blockiert, während die anderen vier Threads in der sum_kernel_tiled Funktion in Zeile 30 aktiv bleiben.

    Sie können die Eigenschaften eines GPU-Threads überprüfen. Sie sind im Fenster "GPU-Threads" in der umfangreichen Dateninfo des Fensters "Parallele Stapel" verfügbar. Um sie anzuzeigen, bewegen Sie den Mauszeiger über den Stapelrahmen von sum_kernel_tiled. Die folgende Abbildung zeigt das Daten-Tooltip.

    DataTip für Parallelstapel-Fenster.
    GPU-Thread-Dateninfo

    Weitere Informationen zum Fenster "Parallele Stapel" finden Sie unter Verwenden des Fensters "Parallele Stapel".

So verwenden Sie das Fenster "Parallele Überwachung"

  1. Um das Fenster Parallelüberwachung zu öffnen, wählen Sie Debug>Windows>Parallelüberwachung>Parallelüberwachung 1 in der Menüleiste aus.

    Sie können das Fenster "Parallele Überwachung " verwenden, um die Werte eines Ausdrucks über mehrere Threads zu prüfen.

  2. Docken Sie das Fenster "Parallel Watch 1 " am unteren Rand von Visual Studio an. Es gibt 32 Zeilen in der Tabelle des Fensters "Parallele Überwachung ". Jedes entspricht einem GPU-Thread, der sowohl im Fenster "GPU-Threads" als auch im Fenster "Parallele Stapel" angezeigt wurde. Jetzt können Sie Ausdrücke eingeben, deren Werte Sie über alle 32 GPU-Threads prüfen möchten.

  3. Wählen Sie die Spaltenüberschrift "Überwachung hinzufügen", geben Sie localIdx ein, und drücken Sie dann die EINGABETASTE.

  4. Wählen Sie die Spaltenüberschrift "Überwachung hinzufügen" erneut aus, geben Sie globalIdxein, und drücken Sie dann die Eingabetaste.

  5. Wählen Sie die Spaltenüberschrift "Überwachung hinzufügen" erneut aus, tippen localA[localIdx[0]]Sie, und wählen Sie dann die EINGABETASTE aus.

    Sie können nach einem angegebenen Ausdruck sortieren, indem Sie die entsprechende Spaltenüberschrift auswählen.

    Wählen Sie die localA[localIdx[0]] Spaltenüberschrift aus, um die Spalte zu sortieren. Die folgende Abbildung zeigt die Ergebnisse der Sortierung nach localA[localIdx[0]].

    Fenster
    Ergebnisse der Sortierung

    Sie können den Inhalt im Fenster "Parallele Überwachung" nach Excel exportieren, indem Sie dieExcel-Schaltfläche und dann "In Excel öffnen" auswählen. Wenn Excel auf Ihrem Entwicklungscomputer installiert ist, öffnet die Schaltfläche ein Excel-Arbeitsblatt, das den Inhalt enthält.

  6. In der oberen rechten Ecke des Fensters "Parallele Überwachung " gibt es ein Filtersteuerelement, mit dem Sie den Inhalt mithilfe von booleschen Ausdrücken filtern können. Geben Sie localA[localIdx[0]] > 20000 in das Textfeld des Filtersteuerelements ein und wählen Sie dann die Eingabetaste aus.

    Das Fenster enthält jetzt nur Threads, auf denen der localA[localIdx[0]] Wert größer als 20000 ist. Der Inhalt wird weiterhin nach der localA[localIdx[0]] Spalte sortiert, bei der es sich um die zuvor ausgewählte Sortieraktion handelt.

Kennzeichnen von GPU-Threads

Sie können bestimmte GPU-Threads markieren, indem Sie sie im Fenster "GPU-Threads", im Fenster "Parallele Überwachung" oder im Fenster "Parallele Stapel" als Dateninfo kennzeichnen. Wenn eine Zeile im GPU-Threads-Fenster mehr als einen Thread enthält, kennzeichnet diese Zeile alle Threads, die in der Zeile enthalten sind.

So kennzeichnen Sie GPU-Threads

  1. Wählen Sie die Spaltenüberschrift [Thread] im Fenster Parallel Watch 1 aus, um nach Kachelindex und Threadindex zu sortieren.

  2. Wählen Sie auf der Menüleiste den Befehl "Debuggen>fortsetzen" aus, wodurch die vier Threads, die aktiv waren, zur nächsten Barriere wechseln (definiert in Zeile 32 von AMPMapReduce.cpp).

  3. Wählen Sie das Kennzeichnungssymbol auf der linken Seite der Zeile aus, das die vier threads enthält, die jetzt aktiv sind.

    Die folgende Abbildung zeigt die vier aktiven gekennzeichneten Threads im GPU-Threads-Fenster .

    GPU-Threads-Fenster mit gekennzeichneten Threads.
    Aktive Threads im GPU-Threadfenster

    Das Fenster "Parallele Überwachung " und die Dateninfo des Fensters "Parallele Stapel" geben beide die gekennzeichneten Threads an.

  4. Wenn Sie sich auf die vier von Ihnen gekennzeichneten Threads konzentrieren möchten, können Sie auswählen, dass nur die gekennzeichneten Threads angezeigt werden. Sie schränkt die Anzeige in den Fenstern für GPU-Threads, Parallelüberwachung und Parallelstapel ein.

    Wählen Sie die Schaltfläche "Nur markierte anzeigen" in einem der Fenster oder auf der Symbolleiste "Debug-Position" aus. Die folgende Abbildung zeigt die Schaltfläche "Nur gekennzeichnet anzeigen" auf der Symbolleiste "Debug-Position".

    Symbolleiste
    Schaltfläche "Nur gekennzeichnete anzeigen"

    Jetzt werden in den Fenstern GPU-Threads, Parallele Überwachung und Parallele Stapel nur die gekennzeichneten Threads angezeigt.

Einfrieren und Auftauen von GPU-Threads

Sie können GPU-Threads im Fenster GPU-Threads oder im Fenster Parallel Watch einfrieren (anhalten) und fortsetzen (wiederaufnehmen). Sie können CPU-Threads auf die gleiche Weise einfrieren und auftauen; siehe How to: Use the Threads Window für weitere Informationen.

Gefrieren und Auftauen von GPU-Threads

  1. Wählen Sie die Schaltfläche "Nur gekennzeichnet anzeigen" aus, um alle Threads anzuzeigen.

  2. Wählen Sie auf der Menüleiste "Debug>Continue" aus.

  3. Öffnen Sie das Kontextmenü für die aktive Zeile und wählen Sie dann Fixieren aus.

    Die folgende Abbildung des GPU-Threads-Fensters zeigt, dass alle vier Threads eingefroren sind.

    GPU-Threads-Fenster mit eingefrorenen Threads.
    Eingefrorene Threads im GPU-Threads-Fenster

    Ebenso zeigt das Fenster "Parallele Überwachung" an, dass alle vier Threads eingefroren sind.

  4. Wählen Sie auf der Menüleiste "Debuggen>fortsetzen" aus, damit die nächsten vier GPU-Threads an der Barriere in Zeile 22 vorbeikommen und den Haltepunkt in Zeile 30 erreichen können. Das GPU-Threads"-Fenster zeigt, dass die vier zuvor eingefrorenen Threads eingefroren bleiben und sich im aktiven Zustand befinden.

  5. Wählen Sie auf der Menüleiste " Debuggen" aus, "Weiter".

  6. Im Fenster "Parallele Überwachung" können Sie auch einzelne oder mehrere GPU-Threads auftauen.

So gruppieren Sie GPU-Threads

  1. Wählen Sie im Kontextmenü für einen der Threads im FENSTER "GPU-Threads " die Option "Gruppieren nach", "Adresse" aus.

    Die Threads im FENSTER "GPU-Threads " werden nach Adresse gruppiert. Die Adresse entspricht der Anweisung bei der Demontage, in der sich jede Gruppe von Threads befindet. 24 Threads befinden sich in Zeile 22, wobei die tile_barrier::wait-Methode ausgeführt wird. 12 Threads befinden sich an der Anweisung für die Barriere in Zeile 32. Vier dieser Threads werden gekennzeichnet. Acht Threads befinden sich am Haltepunkt in Zeile 30. Vier dieser Threads sind eingefroren. Die folgende Abbildung zeigt die gruppierten Threads im GPU-Threads-Fenster .

    GPU-Threads-Fenster mit nach Adresse gruppierten Threads.
    Gruppierte Threads im GPU-Threads-Fenster

  2. Sie können den Vorgang "Gruppieren nach" auch ausführen, indem Sie das Kontextmenü für das Datenraster des Parallel Watch-Fensters öffnen. Wählen Sie Gruppieren nach und dann das Menüelement aus, das entspricht, wie Sie die Threads gruppieren möchten.

Ausführen aller Threads an einer bestimmten Position im Code

Sie führen alle Threads in einer bestimmten Kachel bis zur Zeile mit dem Cursor aus, indem Sie "Aktuelle Kachel bis zum Cursor ausführen" verwenden.

So führen Sie alle Threads an der Position aus, die vom Cursor markiert ist

  1. Wählen Sie im Kontextmenü für die fixierten Threads „Thaw“ aus.

  2. Setzen Sie im Code-Editor den Cursor in Zeile 30.

  3. Wählen Sie im Kontextmenü des Code-Editors die Option "Aktuellen Block bis Cursor ausführen" aus.

    Die 24 Threads, die zuvor an der Barriere in Zeile 21 blockiert wurden, sind in Zeile 32 vorangekommen. Es wird im Fenster "GPU-Threads" angezeigt.

Siehe auch

Übersicht über C++ AMP
Debuggen von GPU-Code
So verwenden Sie das Fenster „GPU-Threads“
How to: Verwenden des Fensters „Parallele Überwachung“
Analyse von C++ AMP-Code mit dem Konkurrenzvisualisierer