Sdílet prostřednictvím


Přehled AMP C++

Knihovna C++ Accelerated Massive Parallelism (C++ AMP) urychluje provádění kódu jazyka C++ využitím hardwaru, umožňujícím paralelní zpracování dat, jako například grafického procesoru (GPU) na samostatné grafické kartě.Pomocí knihovny C++ AMP lze vytvořit kód algoritmů, pracujících s vícerozměrnými daty tak, že jejich provádění bude urychleno pomocí paralelismu na heterogenním hardwaru.Model programování knihovny C++ AMP zahrnuje vícerozměrná pole, indexování, přenos paměti, rozložení a knihovnu matematických funkcí.Jazyková rozšíření, obsažená v knihovně C++ AMP, lze použít pro ovládání toho, jak jsou data přenášena z procesoru do GPU a zpět, a tak zvýšit výkon.

Požadavky na systém

  • Windows 7, Windows 8, Windows Server 2008 R2 nebo Windows Server 2012

  • Rozhraní DirectX 11 funkce úrovně 11.0 nebo novější hardware

  • Pro ladění v softwarovém emulátoru je vyžadován systém Windows 8 nebo systém Windows Server 2012.Pro ladění na hardwaru je nutné nainstalovat ovladače pro grafickou kartu.Další informace naleznete v tématu Ladění kódu GPU.

Úvod

Následující dva příklady ilustrují primární součásti knihovny C++ AMP.Předpokládejme, že je třeba přidat odpovídající prvky dvou jednorozměrných polí.Například může být třeba přidat pole {1, 2, 3, 4, 5} a pole {6, 7, 8, 9, 10} pro získání pole {7, 9, 11, 13, 15}.Bez použití knihovny C++ AMP lze kód pro přidání čísel a zobrazení výsledků zapsat následně.

#include <iostream>

void StandardMethod() {

    int aCPP[] = {1, 2, 3, 4, 5};
    int bCPP[] = {6, 7, 8, 9, 10};
    int sumCPP[5];

    for (int idx = 0; idx < 5; idx++)
    {
        sumCPP[idx] = aCPP[idx] + bCPP[idx];
    }

    for (int idx = 0; idx < 5; idx++)
    {
        std::cout << sumCPP[idx] << "\n";
    }
}

Důležité části kódu jsou:

  • Data: Data se skládají ze tří polí.Všechny mají stejný počet rozměrů (jeden) a délku (pět).

  • Iterace: První smyčka for poskytuje mechanismus pro procházení prvků v polích.Kód, který bude třeba spustit pro výpočet součtu je obsažen v prvním bloku smyčky for.

  • Index: Proměnná idx umožňuje přístup k jednotlivým prvkům polí.

Použitím knihovny C++ AMP lze kód přepsat následujícím způsobem.

#include <amp.h>
#include <iostream>
using namespace concurrency;

const int size = 5;

void CppAmpMethod() {
    int aCPP[] = {1, 2, 3, 4, 5};
    int bCPP[] = {6, 7, 8, 9, 10};
    int sumCPP[size];
    
    // Create C++ AMP objects.
    array_view<const int, 1> a(size, aCPP);
    array_view<const int, 1> b(size, bCPP);
    array_view<int, 1> sum(size, sumCPP);
    sum.discard_data();

    parallel_for_each( 
        // Define the compute domain, which is the set of threads that are created.
        sum.extent, 
        // Define the code to run on each thread on the accelerator.
        [=](index<1> idx) restrict(amp)
    {
        sum[idx] = a[idx] + b[idx];
    }
    );

    // Print the results. The expected output is "7, 9, 11, 13, 15".
    for (int i = 0; i < size; i++) {
        std::cout << sum[i] << "\n";
    }
}

Jsou k dispozici stejné základní prvky, ale jsou použity konstrukce knihovny C++ AMP:

  • Data: Pro tvorbu tří objektů typu array_view knihovny C++ AMP jsou použity pole jazyka C++.Pro tvorbu objektu typu array_view je třeba zadat čtyři hodnoty: datové hodnoty, počet rozměrů, typ elementu a délku objektu typu array_view v každém rozměru.Počet rozměrů a typ jsou předány jako parametry typu.Data a délka jsou předány jako parametry konstruktoru.V tomto příkladu je pole jazyka C++, které je předáno konstruktoru, jednorozměrné.Počet rozměrů a délka jsou v objektu typu array_view použity k tvorbě obdélníkového tvaru dat a datové hodnoty jsou použity k vyplnění pole.Běhová knihovna také zahrnuje třídu Třída Array, která má podobné rozhraní jako třída array_view, která je popsána dále v tomto článku.

  • Iterace: Funkce parallel_for_each funkce (C++ AMP) poskytuje mechanismus pro procházení datových prvků, neboli výpočetní domény.V tomto příkladu je výpočetní doména zadána pomocí sum.extent.Kód, který je třeba spustit, je obsažen ve výrazu lambda, nebo ve funkci jádra.Modifikátor restrict(amp) určuje, že je použita pouze podmnožina jazyka C++, kterou může knihovna C++ AMP urychlit.

  • Index: Proměnná struktury index třídy, idx, je deklarována s rozměrem jedna, aby odpovídala rozměru objektu typu array_view.Pomocí indexu lze přistupovat k jednotlivým prvkům objektu typu array_view.

Tvarování a indexování dat: index a rozsah

Před spuštěním kódu jádra je třeba definovat hodnoty dat a deklarovat tvar dat.Všechna data jsou definována v poli (obdélníkovém) a pole lze definovat tak, že může mít jakékoliv řazení (počet rozměrů).Data mohou mít libovolnou velikost a mít libovolný počet rozměrů.

Hh265136.collapse_all(cs-cz,VS.110).gifTřída index

Struktura index třídy určuje umístění v objektu typu array či v objektu typu array_view zapouzdřením posunu od začátku v každém rozměru do jediného objektu.Při přístupu k umístění v poli je třeba namísto seznamu celočíselných indexů indexujícímu operátoru [] předat objekt typu index.K elementům lze v každém rozměru přistupovat pomocí operátoru Array::Operator() operátora či pomocí operátoru array_view::Operator() operátora.

Následující příklad vytvoří jednorozměrný index, který určuje třetí prvek v jednorozměrném objektu typu array_view.Index je použit ke zobrazení třetího prvku objektu array_view.Výstup je 3.

    
int aCPP[] = {1, 2, 3, 4, 5};
array_view<int, 1> a(5, aCPP);
index<1> idx(2);
std::cout << a[idx] << "\n";  
// Output: 3

Následující příklad vytváří dvojrozměrný index, který určuje prvek, který je v dvourozměrném objektu typu array_view na řádku 1 a ve sloupci 2.První parametr v konstruktoru typu index reprezentuje řádek a druhý parametr zase sloupec.Výstup je 6.

int aCPP[] = {1, 2, 3,
              4, 5, 6};
array_view<int, 2> a(2, 3, aCPP);
index<2> idx(1, 2);
std::cout << a[idx] << "\n";
// Output: 6

Následující příklad vytvoří trojrozměrný index, který určuje prvek, který je v trojrozměrném objektu typu array_view umístěn v hloubce 0, na řádku 1 a ve sloupci 3.Všimněte si, že první parametr je hloubka, druhý parametr řádek a třetí parametr je sloupec.Výstup je 8.

int aCPP[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};

array_view<int, 3> a(2, 3, 4, aCPP); 
// Specifies the element at 3, 1, 0.
index<3> idx(0, 1, 3);               
std::cout << a[idx] << "\n";

// Output: 8

Hh265136.collapse_all(cs-cz,VS.110).gifTřída extent

Struktura rozsahu třídy (C++ AMP) určuje délku dat v každém rozměru objektu typu array nebo objektu typu array_view.Lze vytvořit rozsah a použít jej pro vytvoření objektu array či objektu array_view.Je také možné získat rozsah existujícího objektu typu array či objektu typu array_view.Následující příklad vytiskne délku rozsahu v každém rozměru objektu array_view.

int aCPP[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// There are 3 rows and 4 columns, and the depth is two.
array_view<int, 3> a(2, 3, 4, aCPP); 
std::cout << "The number of columns is " << a.extent[2] << "\n";
std::cout << "The number of rows is " << a.extent[1] << "\n";
std::cout << "The depth is " << a.extent[0]<< "\n";
std::cout << "Length in most significant dimension is " << a.extent[0] << "\n";

V následujícím příkladu je vytvořen objekt typu array_view, který má stejné rozměry jako objekt v předchozím příkladu, ale v tomto příkladu je namísto použití explicitních parametrů v konstruktoru typu array_view použit objekt typu extent.

int aCPP[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24};
extent<3> e(2, 3, 4);
array_view<int, 3> a(e, aCPP);
std::cout << "The number of columns is " << a.extent[2] << "\n";
std::cout << "The number of rows is " << a.extent[1] << "\n";
std::cout << "The depth is " << a.extent[0] << "\n";

Přesun dat na akcelerátor: array a array_view

Pro použití pro přesun dat do akcelerátoru jsou v běhové knihovně definovány dva datové kontejnery.Jedná se o třídu Třída Array a třídu Třída array_view.Třída array je třídou kontejneru, která při vytvoření objektu vytvoří hlubokou kopii dat.Třída array_view je třídou obálky, která zkopíruje data při přístupu funkce jádra k nim.Při potřebě dat na zdrojovém zařízení jsou data zkopírována zpět.

Hh265136.collapse_all(cs-cz,VS.110).gifTřída array

Pokud je použit konstruktor, který obsahuje ukazatel na sadu dat, je při vytvoření objektu typu array v akcelerátoru vytvořena hluboká kopie dat.Funkce jádra modifikuje kopii v akcelerátoru.Po dokončení spuštění funkce jádra je třeba zkopírovat data zpět do struktury zdrojových dat.Následující příklad vynásobí desíti každý prvek vektoru.Po dokončení funkce jádra je pro zkopírování dat zpět do objektu vektoru použit operátor převodu vektoru.

std::vector<int> data(5);
for (int count = 0; count < 5; count++) 
{
     data[count] = count;
}

array<int, 1> a(5, data.begin(), data.end());

parallel_for_each(
    a.extent, 
    [=, &a](index<1> idx) restrict(amp)
    {
        a[idx] = a[idx] * 10;
    }
);

data = a;
for (int i = 0; i < 5; i++) 
{
    std::cout << data[i] << "\n";
}

Hh265136.collapse_all(cs-cz,VS.110).gifTřída array_view

Třída array_view má téměř stejné členy jako třída array, ale základní chování stejné není.Data předaná do konstruktoru třídy array_view nejsou v GPU replikována tak jako v konstruktoru třídy array.Místo toho jsou data při provádění funkce jádra zkopírována na akcelerátor.Proto pokud jsou vytvořeny dva objekty typu array_view, které používají stejná data, oba objekty typu array_view budou odkazovat do stejného paměťového prostoru.Při tomto provedení je třeba synchronizovat vícevláknový přístup.Hlavní výhodou používání třídy array_view je to, že jsou data přesunuta pouze v případě, kdy je to nezbytné.

Hh265136.collapse_all(cs-cz,VS.110).gifPorovnání třídy array a třídy array_view

Následující tabulka shrnuje podobnosti a rozdíly mezi třídou array a třídou array_view.

Description

Třída array

Třída array_view

Kdy je určen počet rozměrů

V době kompilace.

V době kompilace.

Kdy je stanoven rozsah

V době běhu.

V době běhu.

Tvar

Obdélníkový.

Obdélníkový.

Úložiště dat

Je kontejner dat.

Je obálka dat.

Kopírování

Explicitní a hluboká kopie při definici.

Implicitní kopie při přístupu z funkce jádra.

Načítání dat

Kopírování dat pole zpět do objektu ve vlákně procesoru.

Pomocí přímého přístupu objektu typu array_view, nebo pomocí volání metody Metoda array_view::synchronize pro pokračování přístupu k datům v původním kontejneru.

Provádění kódu nad daty: parallel_for_each

Funkce Parallel_for_each definuje kód, který je třeba provést v akcelerátoru nad daty v objektu typu array či objektu typu array_view.Vezměme v potaz následující kód z úvodu tohoto tématu.

#include <amp.h>
#include <iostream>
using namespace concurrency;

void AddArrays() {
    int aCPP[] = {1, 2, 3, 4, 5};
    int bCPP[] = {6, 7, 8, 9, 10};
    int sumCPP[5] = {0, 0, 0, 0, 0};

    array_view<int, 1> a(5, aCPP);
    array_view<int, 1> b(5, bCPP);
    array_view<int, 1> sum(5, sumCPP);

    parallel_for_each(
        sum.extent, 
        [=](index<1> idx) restrict(amp)
        {
            sum[idx] = a[idx] + b[idx];
        }
    );

    for (int i = 0; i < 5; i++) {
        std::cout << sum[i] << "\n";
    }
}

Metoda parallel_for_each přebírá dva argumenty, výpočetní doménu a výraz lambda.

Výpočetní doména je objektem typu extent nebo objektem typu tiled_extent, který definuje sadu vláken pro vytvoření pro paralelní provedení.Pro každý prvek ve výpočetní doméně je generováno jedno vlákno.V tomto případě je objekt typu extent jednorozměrný a má pět prvků.Proto je spuštěno pět vláken.

Výraz lambda definuje kód, který má být v každém vlákně spuštěn.Klauzule sběru [=] určuje to, že tělo výrazu lambda přistupuje ke všem zachyceným proměnným pomocí hodnoty, které jsou v tomto případě a, b a sum.V tomto příkladu vytváří seznam parametrů jednorozměrnou proměnnou typu index nazvanou idx.Hodnota idx[0] je v prvním vlákně nula a v každém následném vlákně se o jednu zvyšuje.Modifikátor restrict(amp) určuje, že je použita pouze podmnožina jazyka C++, kterou může knihovna C++ AMP urychlit.Omezení ve funkcích, které obsahují modifikátor restrict jsou popsány v Klauzule omezení (C++ AMP).Další informace naleznete v tématu Lambda syntaxi výrazu.

Výraz lambda může obsahovat kód pro spuštění, nebo může volat samostatnou funkci jádra.Funkce jádra musí zahrnovat modifikátor restrict(amp).Následující příklad je ekvivalentní k předchozímu, ale volá samostatnou funkci jádra.

#include <amp.h>
#include <iostream>
using namespace concurrency;

void AddElements(index<1> idx, array_view<int, 1> sum, array_view<int, 1> a, array_view<int, 1> b) restrict(amp)
{
    sum[idx] = a[idx] + b[idx];
}


void AddArraysWithFunction() {

    int aCPP[] = {1, 2, 3, 4, 5};
    int bCPP[] = {6, 7, 8, 9, 10};
    int sumCPP[5] = {0, 0, 0, 0, 0};

    array_view<int, 1> a(5, aCPP);
    array_view<int, 1> b(5, bCPP);
    array_view<int, 1> sum(5, sumCPP);

    parallel_for_each(
        sum.extent, 
        [=](index<1> idx) restrict(amp)
        {
            AddElements(idx, sum, a, b);
        }
    );

    for (int i = 0; i < 5; i++) {
        std::cout << sum[i] << "\n";
    }
}

Urychlování kódu: Dlaždice a překážky

Další zrychlení lze získat pomocí rozložení.Rozložení rozděluje vlákna v rovnocenné obdélníkové podmnožiny neboli dlaždice.Velikost odpovídající dlaždice určuje programátor na základě sady dat a algoritmu, který je jím vytvářen.Pro každé vlákno je umožněn přístup ke globálnímu umístění datového prvku, relativnímu k celému objektu typu array, či objektu typu array_view, a přístup k místnímu umístění, relativnímu k dlaždici.Používání hodnoty místního indexu zjednodušuje kód, protože není třeba psát kód pro převod hodnot indexu z globálních na místní.Pro použití rozložení je třeba ve výpočetní doméně v metodě parallel_for_each zavolat metodu rozsah::rozmístit vedle sebe metoda a ve výrazu lambda použít objekt typu tiled_index.

V typických aplikacích prvky v dlaždici nějakým způsobem navzájem souvisejí, kód má přístup k hodnotám a může sledovat hodnoty napříč dlaždicí.k dosažení tohoto cíle je třeba použít klíčové slovo tile_static klíčové slovo a metodu Metoda tile_barrier::wait.Proměnná, u které je použito klíčové slovo tile_static má rozsah přes celou dlaždici a instance proměnné je vytvořena pro každou dlaždici.Je třeba ošetřit synchronizaci přístupu vláken dlaždic k proměnné.Metoda Metoda tile_barrier::wait zastaví provádění aktuálního vlákna, dokud všechna vlákna v dlaždici nedosáhnou volání metody tile_barrier::wait.Takto lze nashromáždit hodnoty napříč dlaždicí pomocí proměnné tile_static.Poté mohou být dokončeny všechny výpočty, které vyžadují přístup ke všem hodnotám.

Následující diagram představuje dvojrozměrné pole dat vzorkování, která jsou uspořádána v dlaždicích.

Hodnoty indexu v vedle sebe rozsahu

Následující příklad kódu používá data vzorkování z předchozího diagramu.Kód nahradí každou hodnotu v dlaždici průměrem hodnot v dlaždici.

// Sample data:
int sampledata[] = {
    2, 2, 9, 7, 1, 4,
    4, 4, 8, 8, 3, 4,
    1, 5, 1, 2, 5, 2,
    6, 8, 3, 2, 7, 2};

// The tiles:
// 2 2    9 7    1 4
// 4 4    8 8    3 4
//
// 1 5    1 2    5 2
// 6 8    3 2    7 2

// Averages:
int averagedata[] = { 
    0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 
    0, 0, 0, 0, 0, 0, 
};

array_view<int, 2> sample(4, 6, sampledata);
array_view<int, 2> average(4, 6, averagedata);

parallel_for_each(
    // Create threads for sample.extent and divide the extent into 2 x 2 tiles.
    sample.extent.tile<2,2>(),
    [=](tiled_index<2,2> idx) restrict(amp)
    {
        // Create a 2 x 2 array to hold the values in this tile.
        tile_static int nums[2][2];
        // Copy the values for the tile into the 2 x 2 array.
        nums[idx.local[1]][idx.local[0]] = sample[idx.global];
        // When all the threads have executed and the 2 x 2 array is complete, find the average.
        idx.barrier.wait();
        int sum = nums[0][0] + nums[0][1] + nums[1][0] + nums[1][1];
        // Copy the average into the array_view.
        average[idx.global] = sum / 4;
      }
);

for (int i = 0; i < 4; i++) {
    for (int j = 0; j < 6; j++) {
        std::cout << average(i,j) << " ";
    }
    std::cout << "\n";
}

// Output:
// 3 3 8 8 3 3
// 3 3 8 8 3 3
// 5 5 2 2 4 4
// 5 5 2 2 4 4

Matematické knihovny

Knihovna C++ AMP obsahuje dvě matematické knihovny.Knihovna s dvojitou přesností v oboru názvů Obor názvů CONCURRENCY::precise_math poskytuje podporu pro funkce s dvojitou přesností.Také poskytuje podporu pro funkce s jednoduchou přesností, přestože je na hardwaru stále vyžadována dvojitá přesnost.Odpovídá specifikaci C99 Specification (ISO/IEC 9899).Akcelerátor musí podporovat plnou dvojitou přesnost.Lze určit, zda ji provede pomocí kontroly hodnoty datového členu Datový člen Accelerator::supports_double_precision.Rychlá matematická knihovna v oboru názvů Obor názvů CONCURRENCY::fast_math obsahuje jinou sadu matematických funkcí.Tyto funkce, které podporují pouze operandy typu float, jsou prováděny rychleji, ale nejsou tak přesné jako ty v matematické knihovně s dvojitou přesností.Funkce jsou obsaženy v hlavičkovém souboru <amp_math.h> a všechny jsou deklarovány s použitím klíčového slova restrict(amp).Funkce v hlavičkovém souboru <cmath> jsou importovány do obou oborů názvů, fast_math a precise_math.Klíčové slovo restrict je použito pro rozlišení verze <cmath> a verze knihovny C++ AMP. Následující kód pomocí rychlé metody vypočítá pro každou hodnotu ve výpočetní doméně dekadický logaritmus.

#include <amp.h>
#include <amp_math.h>
#include <iostream>
using namespace concurrency;


void MathExample() {

    double numbers[] = { 1.0, 10.0, 60.0, 100.0, 600.0, 1000.0 };
    array_view<double, 1> logs(6, numbers);

    parallel_for_each(
        logs.extent,
         [=] (index<1> idx) restrict(amp) {
            logs[idx] = concurrency::fast_math::log10(logs[idx]);
        }
    );

    for (int i = 0; i < 6; i++) {
        std::cout << logs[i] << "\n";
    }
}

Grafická knihovna

Knihovna C++ AMP obsahuje grafickou knihovnu, která je navržena pro urychlené programování grafiky.Tato knihovna se používá pouze u zařízení, která podporují nativní grafické funkce.Metody jsou umístěny v oboru názvů Obor názvů CONCURRENCY::Graphics a jsou obsaženy v hlavičkovém souboru <amp_graphics.h>.Klíčové součásti grafické knihovny jsou:

  • Třída Třída textury: Tuto třídu lze použít pro tvorbu textur z paměti nebo ze souboru.Textury se podobají polím, protože obsahují data a připomínají kontejnery knihovny Standard Template Library (STL) s ohledem na přiřazení a konstrukci kopie.Další informace naleznete v tématu Kontejnery STL.Parametry šablony pro třídu texture jsou typ prvku a počet rozměrů.Počet rozměrů může být 1, 2 nebo 3.Typ prvku může být jeden z typů krátkých vektorů, popsaných dále v tomto článku.

  • Třída Třída writeonly_texture_view: Poskytuje přístup jen pro zápis pro všechny textury.

  • Knihovna Krátké vektorové knihovny: Definuje sadu typů krátkých vektorů o délce 2, 3 a 4, které jsou založeny na typech int, uint, float, double, norm, a nebo unorm.

Windows StoreApps

Stejně jako ostatní knihovny jazyka C++, knihovnu C++ AMP lze použít také v aplikacích pro systém Windows Store.Tyto články popisují jak zahrnout kód, využívající knihovnu C++ AMP, v aplikacích, které jsou vytvořeny pomocí jazyků C++, C#, Visual Basic či JavaScript:

Knihovna C++ AMP a Vizualizér souběžnosti

Vizualizátor souběžnosti zahrnuje podporu pro analýzu výkonu kódu, využívajícího knihovnu C++ AMP.Tyto články popisují tyto funkce:

Doporučení pro výkon

Operace modulo a dělení celých čísel bez znaménka mají výrazně lepší výkon než operace modulo a dělení celých čísel se znaménkem.Je doporučováno použití celých čísel bez znaménka, pokud je to možné.

Viz také

Referenční dokumentace

Lambda syntaxi výrazu

Další zdroje

C++ AMP (masivní rovnoběžnosti Accelerated C++)

Odkaz (C++ AMP)

Blog "Paralelní programování v nativním kódu"