Sdílet prostřednictvím


Návod: Ladění aplikace C++ AMP

Tento článek ukazuje, jak ladit aplikaci, která používá akcelerovaný masivní paralelismus C++ (C++ AMP) k využití grafických procesorů (GPU). Používá paralelní redukční program, který sečte velké množství celých čísel. Tento návod znázorňuje následující úlohy:

  • Spuštění ladicího programu GPU
  • Kontrola vláken GPU v okně Vláken GPU
  • Pomocí okna Paralelní zásobníky můžete současně sledovat zásobníky volání více vláken GPU.
  • Použití okna Paralelní sledování ke kontrole hodnot jednoho výrazu ve více vláknech najednou.
  • Označení, zmrazení, rozmrazování a seskupování vláken GPU
  • Spuštění všech vláken dlaždice do konkrétního umístění v kódu

Požadavky

Než začnete s tímto návodem:

Poznámka:

Hlavičky C++ AMP jsou zastaralé od sady Visual Studio 2022 verze 17.0. Zahrnutím všech hlaviček AMP se vygenerují chyby sestavení. Před zahrnutím záhlaví AMP definujte _SILENCE_AMP_DEPRECATION_WARNINGS upozornění.

  • Přečtěte si přehled C++ AMP.
  • Ujistěte se, že se v textovém editoru zobrazují čísla řádků. Další informace naleznete v tématu Postupy: Zobrazení čísel řádků v editoru.
  • Ujistěte se, že používáte alespoň Windows 8 nebo Windows Server 2012 a podporujete ladění v emulátoru softwaru.

Poznámka:

Váš počítač může v následujících pokynech zobrazovat odlišné názvy nebo umístění některých prvků uživatelského rozhraní sady Visual Studio. Tyto prvky jsou určeny edicí sady Visual Studio a použitým nastavením. Další informace najdete v tématu Přizpůsobení integrovaného vývojového prostředí.

Vytvoření ukázkového projektu

Pokyny k vytvoření projektu se liší podle toho, jakou verzi sady Visual Studio používáte. Ujistěte se, že máte na této stránce vybranou správnou verzi dokumentace.

Vytvoření ukázkového projektu v sadě Visual Studio

  1. Na řádku nabídek zvolte Soubor>nový>projekt a otevřete dialogové okno Vytvořit nový projekt.

  2. V horní části dialogového okna nastavte jazyk na C++, nastavte platformu pro Windows a nastavte typ projektu na konzolu.

  3. V filtrovaném seznamu typů projektů zvolte Konzolová aplikace a pak zvolte Další. Na další stránce zadejte AMPMapReduce do pole Název název projektu a zadejte umístění projektu, pokud chcete jiný.

    Snímek obrazovky s dialogovým oknem Vytvořit nový projekt s vybranou šablonou konzolové aplikace

  4. Zvolte tlačítko Vytvořit a vytvořte projekt klienta.

Vytvoření ukázkového projektu v sadě Visual Studio 2017 nebo Visual Studio 2015

  1. Spusťte Visual Studio.

  2. Na řádku nabídek zvolte Soubor>nový>projekt.

  3. V části Nainstalované v podokně šablon zvolte Visual C++.

  4. Zvolte konzolovou aplikaci Win32, zadejte AMPMapReduce do pole Název a pak zvolte tlačítko OK .

  5. Zvolte tlačítko Další.

  6. Zrušte zaškrtnutí políčka Předkompilované záhlaví a pak zvolte tlačítko Dokončit.

  7. V Průzkumník řešení odstraňte z projektu stdafx.h, targetver.h a stdafx.cpp.

Další:

  1. Otevřete AMPMapReduce.cpp a nahraďte jeho obsah následujícím kódem.

    // 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 řádku nabídek zvolte Soubor>uložit vše.

  3. V Průzkumník řešení otevřete místní nabídku pro AMPMapReduce a pak zvolte Vlastnosti.

  4. V dialogovém okně Stránky vlastností v části Vlastnosti konfigurace zvolte předkompilované hlavičky C/C++>.

  5. U vlastnosti Předkompilovaná hlavička vyberte Možnost Nepoučovat předkompilované hlavičky a pak zvolte tlačítko OK.

  6. Na řádku nabídek zvolte Sestavit>řešení.

Ladění kódu procesoru

V tomto postupu použijete místní ladicí program systému Windows k ověření správnosti kódu procesoru v této aplikaci. Segment kódu procesoru v této aplikaci, který je zvlášť zajímavý, je smyčka for reduction_sum_gpu_kernel ve funkci. Řídí snížení paralelně založené na stromové struktuře, které běží na GPU.

Ladění kódu procesoru

  1. V Průzkumník řešení otevřete místní nabídku pro AMPMapReduce a pak zvolte Vlastnosti.

  2. V dialogovém okně Stránky vlastností v části Vlastnosti konfigurace zvolte Ladění. Ověřte, že je v ladicím programu pro spuštění vybrán místní ladicí program systému Windows.

  3. Vraťte se do Editoru kódu.

  4. Nastavte zarážky na řádcích kódu zobrazeném na následujícím obrázku (přibližně 67 řádků 70).

    Zarážky procesoru označené vedle řádků kódu v editoru
    Zarážky procesoru

  5. Na řádku nabídek zvolte >Spustit ladění.

  6. V okně Místní hodnoty sledujte hodnotu stride_size , dokud se nedosáhne zarážky na řádku 70.

  7. Na řádku nabídek zvolte Ladění>zastavit ladění.

Ladění kódu GPU

Tato část ukazuje, jak ladit kód GPU, což je kód obsažený ve sum_kernel_tiled funkci. Kód GPU vypočítá součet celých čísel pro každý blok paralelně.

Ladění kódu GPU

  1. V Průzkumník řešení otevřete místní nabídku pro AMPMapReduce a pak zvolte Vlastnosti.

  2. V dialogovém okně Stránky vlastností v části Vlastnosti konfigurace zvolte Ladění.

  3. V ladicím programu ke spuštění seznamu vyberte Místní ladicí program systému Windows.

  4. V seznamu Typ ladicího programu ověřte, zda je vybrána možnost Automaticky.

    Automaticky je výchozí hodnota. Ve verzích před Windows 10 je pouze požadovaná hodnota GPU místo automatického použití.

  5. Zvolte tlačítko OK.

  6. Nastavte zarážku na řádku 30, jak je znázorněno na následujícím obrázku.

    Zarážky GPU označené vedle řádku kódu v editoru
    Zarážka GPU

  7. Na řádku nabídek zvolte >Spustit ladění. Zarážky v kódu procesoru na řádcích 67 a 70 se nespustí během ladění GPU, protože tyto řádky kódu běží na procesoru.

Použití okna vláken GPU

  1. Pokud chcete otevřít okno Vlákna GPU, na řádku nabídek zvolte Ladit>vlákna GPU systému Windows.>

    Stav vláken GPU můžete zkontrolovat v okně Vlákna GPU, které se zobrazí.

  2. Ukotvení okna vláken GPU v dolní části sady Visual Studio Kliknutím na tlačítko Rozbalit přepínač vlákna zobrazíte textová pole dlaždice a vlákna. Okno Vlákna GPU zobrazuje celkový počet aktivních a blokovaných vláken GPU, jak je znázorněno na následujícím obrázku.

    Okno Vláken GPU se 4 aktivními vlákny
    Okno Vláken GPU

    313 dlaždic se přidělí pro tento výpočet. Každá dlaždice obsahuje 32 vláken. Vzhledem k tomu, že místní ladění GPU probíhá v emulátoru softwaru, existují čtyři aktivní vlákna GPU. Čtyři vlákna spustí instrukce současně a pak se přesunou k další instrukci.

    V okně Vláken GPU jsou aktivní čtyři vlákna GPU a 28 vláken GPU blokované v tile_barrier::wait příkaz definovaný asi na řádku 21 (t_idx.barrier.wait();). Všechna vlákna GPU 32 patří k první dlaždici, tile[0]. Šipka odkazuje na řádek, který obsahuje aktuální vlákno. Pokud chcete přepnout na jiné vlákno, použijte jednu z následujících metod:

    • V řádku pro vlákno, na který se má přepnout v okně Vlákna GPU, otevřete místní nabídku a zvolte Přepnout na vlákno. Pokud řádek představuje více než jedno vlákno, přepnete na první vlákno podle souřadnic vláken.

    • Do odpovídajících textových polí zadejte hodnoty dlaždice a vlákna vlákna a pak zvolte tlačítko Přepnout vlákno .

    V okně Zásobník volání se zobrazí zásobník volání aktuálního vlákna GPU.

Použití okna Paralelní zásobníky

  1. Pokud chcete otevřít okno Paralelní zásobníky, na řádku nabídek zvolte Ladit>paralelní zásobníky Windows.>

    Okno Paralelní zásobníky můžete použít k souběžné kontrole rámců zásobníku více vláken GPU.

  2. Ukotvení okna Paralelní zásobníky v dolní části sady Visual Studio

  3. Ujistěte se, že je v seznamu v levém horním rohu vybraná vlákna. Na následujícím obrázku znázorňuje okno Paralelní zásobníky zobrazení vláken GPU, která jste viděli v okně Vlákna GPU.

    Okno Paralelní zásobníky se 4 aktivními vlákny
    Okno Paralelní zásobníky

    32 vláken přešlo z _kernel_stub příkazu lambda ve parallel_for_each volání funkce a potom na sum_kernel_tiled funkci, kde dochází k paralelní redukci. 28 z 32 vláken pokročilo na tile_barrier::wait příkaz a zůstává blokováno na řádku 22, zatímco ostatní čtyři vlákna zůstávají aktivní ve sum_kernel_tiled funkci na řádku 30.

    Můžete zkontrolovat vlastnosti vlákna GPU. Jsou k dispozici v okně Vláken GPU v bohatém datovém tipu okna Paralelní zásobníky . Pokud je chcete zobrazit, najeďte myší na rámeček zásobníku sum_kernel_tiled. Následující obrázek znázorňuje Popis dat.

    Okno DataTip pro paralelní zásobníky
    Popis dat ve vlákně GPU

    Další informace o okně Paralelní zásobníky naleznete v části Použití okna Paralelní zásobníky.

Použití okna Paralelní sledování

  1. Pokud chcete otevřít okno Paralelní sledování, na řádku nabídek zvolte Ladit>paralelní hodinky>Windows>Parallel Watch 1.

    Okno Paralelní kukátko můžete použít ke kontrole hodnot výrazu ve více vláknech.

  2. Ukotvit okno Paralelní kukátka 1 do dolní části sady Visual Studio V tabulce okna Paralelní kukátko je 32 řádků. Každý odpovídá vláknu GPU, které se objevilo v okně Vláken GPU i v okně Paralelní zásobníky . Teď můžete zadat výrazy, jejichž hodnoty chcete zkontrolovat ve všech 32 vláknech GPU.

  3. Vyberte záhlaví sloupce Přidat kukátku, zadejte localIdxa pak zvolte klávesu Enter.

  4. Znovu vyberte záhlaví sloupce Přidat kukátku, zadejte globalIdxa pak zvolte klávesu Enter.

  5. Znovu vyberte záhlaví sloupce Přidat kukátku, zadejte localA[localIdx[0]]a pak zvolte klávesu Enter.

    Můžete řadit podle zadaného výrazu tak, že vyberete odpovídající záhlaví sloupce.

    Pokud chcete sloupec seřadit, vyberte záhlaví sloupce localA[localIdx[0]]. Následující obrázek znázorňuje výsledky řazení podle localA[localIdx[0]].

    Okno Paralelní kukátko se seřazenými výsledky
    Výsledky řazení

    Obsah v okně Paralelní kukátko můžete exportovat do Excelu tak, že vyberete tlačítko Excelu a pak zvolíte Otevřít v Excelu. Pokud máte na vývojovém počítači nainstalovaný Excel, tlačítko otevře excelový list, který obsahuje obsah.

  6. V pravém horním rohu okna Paralelní kukátko je ovládací prvek filtru, který můžete použít k filtrování obsahu pomocí logických výrazů. Zadejte localA[localIdx[0]] > 20000 do textového pole ovládacího prvku filtru a pak zvolte klávesu Enter .

    Okno teď obsahuje pouze vlákna, na kterých localA[localIdx[0]] je hodnota větší než 2 0000. Obsah je stále seřazen podle localA[localIdx[0]] sloupce, což je akce řazení, kterou jste zvolili dříve.

Označení vláken GPU příznakem

Konkrétní vlákna GPU můžete označit příznakem v okně Vlákna GPU, v okně Paralelní kukátko nebo v okně Paralelní zásobníky. Pokud řádek v okně Vláken GPU obsahuje více než jedno vlákno, označí tento řádek příznakem všechna vlákna obsažená v řádku.

Označení vláken GPU příznakem

  1. Výběrem záhlaví sloupce [Vlákno] v okně Paralelní kukátko 1 seřadíte podle indexu dlaždic a indexu vlákna.

  2. Na řádku nabídek zvolte Pokračovat ladění>, což způsobí, že čtyři vlákna, která byla aktivní, budou pokračovat na další bariéru (definovaná na řádku 32 AMPMapReduce.cpp).

  3. Zvolte symbol příznaku na levé straně řádku, který obsahuje čtyři vlákna, která jsou nyní aktivní.

    Následující obrázek znázorňuje čtyři aktivní vlákna s příznakem v okně Vlákna GPU.

    Okno Vláken GPU s vlákny označenými příznakem
    Aktivní vlákna v okně Vláken GPU

    Okno Paralelní kukátko a popis okna Paralelní zásobníky označují vlákna s příznakem.

  4. Pokud se chcete zaměřit na čtyři vlákna, která jste označili příznakem, můžete se rozhodnout zobrazit pouze vlákna s příznakem. Omezuje to, co vidíte v oknech vláken GPU, paralelních kukátkách a paralelních zásobníkech .

    Zvolte tlačítko Zobrazit pouze příznak na libovolném okně nebo na panelu nástrojů Umístění ladění. Následující obrázek ukazuje tlačítko Zobrazit pouze příznak na panelu nástrojů Umístění ladění.

    Panel nástrojů Umístění ladění s ikonou Zobrazit pouze označený příznakem
    Show Flagged Only button

    Teď okna vláken GPU, paralelního sledování a paralelních zásobníků zobrazují jenom vlákna s příznakem.

Ukotvení a rozmrazování vláken GPU

Můžete ukotvit (pozastavit) a rozmrazit (obnovit) vlákna GPU z okna Vláken GPU nebo okna Paralelní kukátko . Vlákna procesoru můžete ukotvit a rozmrazit stejným způsobem; Informace naleznete v tématu Postupy: Použití okna Vlákna.

Ukotvení a rozmrznutí vláken GPU

  1. Pokud chcete zobrazit všechna vlákna, zvolte tlačítko Zobrazit pouze příznak.

  2. Na řádku nabídek zvolte Pokračovat ladění>.

  3. Otevřete místní nabídku aktivního řádku a pak zvolte Ukotvit.

    Následující obrázek okna vláken GPU ukazuje, že jsou zablokovaný všechny čtyři vlákna.

    Okna vláken GPU zobrazující ukotvená vlákna
    Ukotvená vlákna v okně Vláken GPU

    Podobně okno Paralelní kukátko ukazuje, že jsou zablokovaný všechny čtyři vlákna.

  4. Na řádku nabídek zvolte Ladit>pokračovat, aby následující čtyři vlákna GPU pokračovala kolem bariéry na řádku 22 a dosáhla zarážky na řádku 30. Okno Vláken GPU ukazuje, že čtyři dříve ukotvená vlákna zůstávají zablokovaný a v aktivním stavu.

  5. Na řádku nabídek zvolte Ladit, Pokračovat.

  6. V okně Paralelní kukátko můžete také rozmrazit jednotlivá nebo více vláken GPU.

Seskupení vláken GPU

  1. V místní nabídce pro jedno z vláken v okně Vláken GPU zvolte Seskupovat podle, Adresa.

    Vlákna v okně Vláken GPU jsou seskupené podle adresy. Adresa odpovídá pokynu k demontáži, kde se nachází každá skupina vláken. 24 vláken jsou na řádku 22, kde se spustí metoda tile_barrier::wait . 12 vláken jsou na pokyn pro bariéru na řádku 32. Čtyři z těchto vláken jsou označeny příznakem. Osm vláken je na zarážce na řádku 30. Čtyři z těchto vláken jsou zmrazeny. Následující obrázek znázorňuje seskupené vlákna v okně Vlákna GPU.

    Okno Vlákna GPU seskupenými vlákny podle adresy
    Seskupené vlákna v okně Vláken GPU

  2. Operaci Seskupit můžete provést také tak, že otevřete místní nabídku pro datovou mřížku okna Paralelní kukátko. Vyberte Možnost Seskupit podle a pak zvolte položku nabídky, která odpovídá způsobu seskupení vláken.

Spuštění všech vláken do konkrétního umístění v kódu

Spustíte všechna vlákna v dané dlaždici na řádek, který obsahuje kurzor pomocí příkazu Spustit aktuální dlaždici na kurzor.

Spuštění všech vláken do umístění označeného kurzorem

  1. V místní nabídce pro ukotvená vlákna zvolte Thaw.

  2. V Editoru kódu umístěte kurzor na řádek 30.

  3. V místní nabídce editoru kódu zvolte Spustit aktuální dlaždici kurzoru.

    24 závitů, které byly dříve blokovány na bariérě na řádku 21, postupovaly na linku 32. Zobrazuje se v okně Vláken GPU.

Viz také

Přehled C++ AMP
Ladění kódu GPU
Postupy: Použití okna vláken GPU
Postupy: Použití okna paralelního sledování
Analýza kódu C++ AMP pomocí Vizualizéru souběžnosti