Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Anmärkning
C++ AMP-huvuden är inaktuella från och med Visual Studio 2022 version 17.0.
Om du inkluderar AMP headers uppstår byggfel. Definiera _SILENCE_AMP_DEPRECATION_WARNINGS innan du inkluderar AMP-huvuden för att undvika varningarna.
C++ Accelererad massiv parallellitet (C++ AMP) påskyndar körningen av C++-kod genom att dra nytta av dataparallell maskinvara, till exempel en grafikprocessor (GPU) på ett diskret grafikkort. Med hjälp av C++ AMP kan du koda flerdimensionella dataalgoritmer så att körningen kan påskyndas med hjälp av parallellitet på heterogen maskinvara. Programmeringsmodellen C++ AMP innehåller flerdimensionella matriser, indexering, minnesöverföring, tiling och ett matematiskt funktionsbibliotek. Du kan använda språktilläggen C++ AMP för att styra hur data flyttas från processorn till GPU:n och tillbaka, så att du kan förbättra prestandan.
Systemkrav
- Windows 7 eller senare 
- Windows Server 2008 R2 genom Visual Studio 2019. 
- DirectX 11-funktionsnivå 11.0 eller senare maskinvara 
- För felsökning på programvaruemulatorn krävs Windows 8 eller Windows Server 2012. För felsökning på maskinvaran måste du installera drivrutinerna för grafikkortet. Mer information finns i Felsöka GPU-kod. 
- Obs! AMP stöds för närvarande inte på ARM64. 
Inledning
Följande två exempel illustrerar de primära komponenterna i C++ AMP. Anta att du vill lägga till motsvarande element i två endimensionella matriser. Du kanske till exempel vill lägga till {1, 2, 3, 4, 5} och {6, 7, 8, 9, 10} för att få {7, 9, 11, 13, 15}. Utan att använda C++ AMP kan du skriva följande kod för att lägga till talen och visa resultatet.
#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";
    }
}
De viktiga delarna i koden är följande:
- Data: Data består av tre matriser. Alla har samma rangordning (en) och längd (fem). 
- Iteration: Den första - forloopen ger en mekanism för iterering genom elementen i matriserna. Den kod som du vill köra för att beräkna summorna finns i det första- forblocket.
- Index: Variabeln - idxkommer åt de enskilda elementen i matriserna.
Med C++ AMP kan du skriva följande kod i stället.
#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";
    }
}
Samma grundläggande element finns, men C++ AMP-konstruktioner används:
- Data: Du använder C++-matriser för att konstruera tre C++ AMP-array_view objekt. Du anger fyra värden för att konstruera ett - array_viewobjekt: datavärdena, rangordningen, elementtypen och längden på- array_viewobjektet i varje dimension. Rangordningen och typen skickas som typparametrar. Data och längd skickas som konstruktorparametrar. I det här exemplet är C++-matrisen som skickas till konstruktorn endimensionell. Rangordningen och längden används för att konstruera den rektangulära formen av data i- array_viewobjektet, och datavärdena används för att fylla matrisen. Runtime-biblioteket innehåller även matrisklassen, som har ett gränssnitt som liknar- array_viewklassen och beskrivs senare i den här artikeln.
- Iteration: Funktionen parallel_for_each (C++ AMP) ger en mekanism för iterering genom dataelementen eller beräkningsdomänen. I det här exemplet anges beräkningsdomänen av - sum.extent. Koden som du vill köra finns i ett lambda-uttryck eller en kernelfunktion.- restrict(amp)Anger att endast den delmängd av C++-språket som C++ AMP kan accelerera används.
- Index: Indexklassvariabeln , - idxdeklareras med rangordningen en för att matcha objektets- array_viewrangordning. Med hjälp av indexet kan du komma åt objektens- array_viewenskilda element.
Forma och indexera data: index och omfattning
Du måste definiera datavärdena och deklarera dataformen innan du kan köra kernelkoden. Alla data definieras som en matris (rektangulär) och du kan definiera matrisen så att den har valfri rangordning (antal dimensioner). Data kan vara valfri storlek i någon av dimensionerna.
indexklass
              Indexklassen anger en plats i array objektet eller array_view genom att kapsla in förskjutningen från ursprunget i varje dimension till ett objekt. När du kommer åt en plats i matrisen skickar du ett index objekt till indexeringsoperatorn, []i stället för en lista med heltalsindex. Du kan komma åt elementen i varje dimension med hjälp av matrisen::operator() operator eller operatorn array_view::operator().
I följande exempel skapas ett endimensionellt index som anger det tredje elementet i ett endimensionellt array_view objekt. Indexet används för att skriva ut det tredje elementet array_view i objektet. Resultatet är 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
I följande exempel skapas ett tvådimensionellt index som anger elementet där raden = 1 och kolumnen = 2 i ett tvådimensionellt array_view objekt. Den första parametern i index konstruktorn är radkomponenten och den andra parametern är kolumnkomponenten. Outputen är 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
I följande exempel skapas ett tredimensionellt index som anger elementet där djupet = 0, raden = 1 och kolumnen = 3 i ett tredimensionellt array_view objekt. Observera att den första parametern är djupkomponenten, den andra parametern är radkomponenten och den tredje parametern är kolumnkomponenten. Resultatet är 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
omfattningsklass
              Utsträckningsklassen anger längden på data i varje dimension av array objektet eller array_view . Du kan skapa en omfattning och använda den för att skapa ett array eller array_view objekt. Du kan också hämta omfattningen av ett befintligt array objekt eller array_view objekt. I följande exempel skrivs längden på omfattningen ut i varje dimension av ett array_view objekt.
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";
I följande exempel skapas ett array_view objekt som har samma dimensioner som objektet i föregående exempel, men i det här exemplet används ett extent objekt i stället för explicita parametrar i array_view konstruktorn.
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";
Flytta data till acceleratorn: matris och array_view
Två datacontainrar som används för att flytta data till acceleratorn definieras i körningsbiblioteket. De är matrisklassen och array_view-klassen. Klassen array är en containerklass som skapar en djup kopia av data när objektet skapas. Klassen array_view är en omslutningsklass som kopierar data när kernelfunktionen kommer åt data. När data behövs på källenheten kopieras data tillbaka.
matrisklass
När ett array objekt skapas skapas en djup kopia av data på acceleratorn om du använder en konstruktor som innehåller en pekare till datauppsättningen. Kernelfunktionen ändrar kopian på acceleratorn. När körningen av kernelfunktionen är klar måste du kopiera tillbaka data till källdatastrukturen. I följande exempel multipliceras varje element i en vektor med 10. När kernelfunktionen är klar används den vector conversion operator för att kopiera tillbaka data till vektorobjektet.
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";
}
array_view-klass
Har array_view nästan samma medlemmar som array klassen, men det underliggande beteendet är inte detsamma. Data som skickas till array_view konstruktorn replikeras inte på GPU:n som med en array konstruktor. I stället kopieras data till acceleratorn när kernelfunktionen körs. Om du skapar två array_view objekt som använder samma data refererar därför båda array_view objekten till samma minnesutrymme. När du gör detta måste du synkronisera all flertrådad åtkomst. Den största fördelen med att använda array_view klassen är att data endast flyttas om det behövs.
Jämförelse av array och array_view
I följande tabell sammanfattas likheterna och skillnaderna mellan klasserna array och array_view .
| Beskrivning | matrisklass | array_view-klass | 
|---|---|---|
| När rangordning bestäms | Vid kompileringstid. | Vid kompileringstid. | 
| När omfattningen bestäms | Vid körning. | Vid körning. | 
| Form | Rektangulär. | Rektangulär. | 
| Datalagring | Är en datacontainer. | Är ett dataomslag. | 
| Kopiera | Explicit och djupkopiering vid definitionens början. | Implicit kopia när den används av kernelfunktionen. | 
| Datahämtning | Genom att kopiera matrisdata tillbaka till ett objekt i CPU-tråden. | Genom direkt åtkomst till array_viewobjektet eller genom att anropa metoden array_view::synkronisera för att fortsätta komma åt data i den ursprungliga containern. | 
Delat minne med matris och array_view
Delat minne är minne som kan nås av både processorn och acceleratorn. Användningen av delat minne eliminerar eller minskar avsevärt kostnaderna för att kopiera data mellan processorn och acceleratorn. Även om minnet delas kan det inte nås samtidigt av både processorn och acceleratorn, och detta orsakar odefinierat beteende.
              array objekt kan användas för att ange detaljerad kontroll över användningen av delat minne om den associerade acceleratorn stöder det. Om en accelerator stöder delat minne bestäms av acceleratorns supports_cpu_shared_memory egenskap, som returnerar true när delat minne stöds. Om delat minne stöds bestäms standardvärdet för uppräkningen access_type för minnesallokeringar på acceleratorn av egenskapen . Som standardinställning får array och array_view objekt samma access_type egenskap som den primära associerade accelerator.
Genom att ange egenskapen array::cpu_access_type Data Member för en array explicit kan du utöva detaljerad kontroll över hur delat minne används, så att du kan optimera appen för maskinvarans prestandaegenskaper, baserat på minnesåtkomstmönstren för dess beräkningskärnor. En array_view återspeglar samma cpu_access_type som den som den array är associerad med, eller, om array_view är konstruerad utan en datakälla, återspeglar den access_type den miljö som först gör att den allokerar lagring. Om den först nås av värden (CPU) beter den sig som om den skapades över en CPU-datakälla och delar access_type av accelerator_view som är associerad med avbildningen. Men om den först används av en accelerator_view, beter den sig som om den skapades över en array som skapades på den accelerator_view och delar arrayaccess_type.
I följande kodexempel visas hur du avgör om standardacceleratorn stöder delat minne och sedan skapar flera matriser som har olika cpu_access_type konfigurationer.
#include <amp.h>
#include <iostream>
using namespace Concurrency;
int main()
{
    accelerator acc = accelerator(accelerator::default_accelerator);
    // Early out if the default accelerator doesn't support shared memory.
    if (!acc.supports_cpu_shared_memory)
    {
        std::cout << "The default accelerator does not support shared memory" << std::endl;
        return 1;
    }
    // Override the default CPU access type.
    acc.default_cpu_access_type = access_type_read_write
    // Create an accelerator_view from the default accelerator. The
    // accelerator_view inherits its default_cpu_access_type from acc.
    accelerator_view acc_v = acc.default_view;
    // Create an extent object to size the arrays.
    extent<1> ex(10);
    // Input array that can be written on the CPU.
    array<int, 1> arr_w(ex, acc_v, access_type_write);
    // Output array that can be read on the CPU.
    array<int, 1> arr_r(ex, acc_v, access_type_read);
    // Read-write array that can be both written to and read from on the CPU.
    array<int, 1> arr_rw(ex, acc_v, access_type_read_write);
}
Kör koden över data: parallel_for_each
Funktionen parallel_for_each definierar den kod som du vill köra på acceleratorn mot data i array objektet eller array_view . Överväg följande kod från introduktionen av det här avsnittet.
#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";
    }
}
Metoden parallel_for_each tar två argument, en beräkningsdomän och ett lambda-uttryck.
              Beräkningsdomänen är ett extent objekt eller ett tiled_extent objekt som definierar den uppsättning trådar som ska skapas för parallell körning. En tråd genereras för varje element i beräkningsdomänen. I det här fallet är objektet extent endimensionellt och har fem element. Därför startas fem trådar.
              Lambda-uttrycket definierar koden som ska köras på varje tråd. Infångningssatsen, [=] anger att lambda-uttryckets huvuddel har åtkomst till alla infångade variabler som värden, vilka i det här fallet är a, b och sum. I det här exemplet skapar parameterlistan en endimensionell index variabel med namnet idx. Värdet för idx[0] är 0 i den första tråden och ökar med en i varje efterföljande tråd. 
              restrict(amp) Anger att endast den delmängd av C++-språket som C++ AMP kan accelerera används.  Begränsningarna för funktioner som har begränsningsmodifieraren beskrivs i begränsa (C++ AMP). Mer information finns i Lambda-uttryckssyntax.
Lambda-uttrycket kan innehålla koden som ska köras eller anropa en separat kernelfunktion. Kernel-funktionen måste innehålla restrict(amp) modifieraren. Följande exempel motsvarar föregående exempel, men det anropar en separat kernelfunktion.
#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";
    }
}
Accelererande kod: Paneler och hinder
Du kan få ytterligare acceleration med hjälp av plattsättning. Tiling delar upp trådarna i lika rektangulära delmängder eller plattor. Du bestämmer lämplig panelstorlek baserat på din datauppsättning och den algoritm som du kodar. För varje tråd har du åtkomst till den globala platsen för ett dataelement i förhållande till hela array eller array_view och åtkomst till den lokala platsen i förhållande till panelen. När du använder det lokala indexvärdet förenklas koden eftersom du inte behöver skriva koden för att översätta indexvärden från global till lokal. Om du vill använda tiling anropar du metoden extent::tile på beräkningsdomänen parallel_for_each i metoden och använder ett tiled_index-objekt i lambda-uttrycket.
I vanliga program är elementen i en panel relaterade på något sätt, och koden måste komma åt och hålla reda på värden i panelen. Använd nyckelordet tile_static och tile_barrier::wait-metoden för att åstadkomma detta. En variabel som har nyckelordet tile_static har ett omfång i en hel panel och en instans av variabeln skapas för varje panel. Du måste hantera synkronisering av paneltrådsåtkomst till variabeln. Metoden tile_barrier::wait stoppar körningen av den aktuella tråden tills alla trådar i panelen har nått anropet till tile_barrier::wait. Så du kan samla värden över panelen med hjälp av tile_static variabler. Sedan kan du slutföra alla beräkningar som kräver åtkomst till alla värden.
Följande diagram representerar en tvådimensionell matris med samplingsdata som är ordnad i paneler.
               
              
            
I följande kodexempel används samplingsdata från föregående diagram. Koden ersätter varje värde i panelen med medelvärdet av värdena i panelen.
// 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
Matematiska bibliotek
C++ AMP innehåller två matematiska bibliotek. 
              Concurrency::precise_math-namnområdet ger stöd för funktioner med dubbla precisionsbibliotek. Det ger också stöd för funktioner med enkel precision, även om stöd för dubbel precision på maskinvaran fortfarande krävs. Den överensstämmer med C99-specifikationen (ISO/IEC 9899). Acceleratorn måste ha stöd för full dubbel precision. Du kan avgöra om det gör det genom att kontrollera värdet på accelerator::supports_double_precision Data Member. Det snabba matematiska biblioteket i namnområdet Concurrency::fast_math innehåller en annan uppsättning matematiska funktioner. Dessa funktioner, som endast float stöder operander, körs snabbare men är inte lika exakta som de i matematikbiblioteket med dubbel precision. Funktionerna finns i <huvudfilen amp_math.h> och alla deklareras med restrict(amp). Funktionerna i <cmath-huvudfilen> importeras till både namnrymderna fast_math och precise_math . Nyckelordet restrict används för att särskilja <cmath-versionen> och C++ AMP-versionen. Följande kod beräknar logaritmen base-10 med hjälp av den snabba metoden för varje värde som finns i beräkningsdomänen.
#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(numbers[idx]);
        }
    );
    for (int i = 0; i < 6; i++) {
        std::cout << logs[i] << "\n";
    }
}
Grafikbibliotek
C++ AMP innehåller ett grafikbibliotek som är utformat för accelererad grafikprogrammering. Det här biblioteket används endast på enheter som stöder inbyggda grafikfunktioner. Metoderna finns i namnområdet Concurrency::graphics och finns i <huvudfilen amp_graphics.h> . De viktigaste komponenterna i grafikbiblioteket är:
- texturklass: Du kan använda texturklassen för att skapa texturer från minnet eller från en fil. Texturer liknar matriser eftersom de innehåller data, och de liknar containrar i C++-standardbiblioteket när det gäller tilldelning och kopieringskonstruktion. Mer information finns i C++ Standard-bibliotekscontainrar. Mallparametrarna för - textureklassen är elementtypen och rangordningen. Rangordningen kan vara 1, 2 eller 3. Elementtypen kan vara en av de korta vektortyper som beskrivs senare i den här artikeln.
- writeonly_texture_view-klass: Ger skrivskyddad åtkomst till valfri struktur. 
- Kort vektorbibliotek: Definierar en uppsättning korta vektortyper av längd 2, 3 och 4 som baseras på - int,- uint,- float,- double, norm eller unorm.
UWP-appar (Universal Windows Platform)
Precis som andra C++-bibliotek kan du använda C++ AMP i dina UWP-appar. I de här artiklarna beskrivs hur du inkluderar C++ AMP-kod i appar som skapas med hjälp av C++, C#, Visual Basic eller JavaScript:
- Genomgång: Skapa en grundläggande Windows Runtime-komponent i C++ och anropa den från JavaScript 
- Bing Maps Trip Optimizer, en Window Store-app i JavaScript och C++ 
- Så här använder du C++ AMP från C# med hjälp av Windows Runtime 
C++ AMP och Concurrency Visualizer
Concurrency Visualizer har stöd för att analysera prestanda för C++ AMP-kod. I de här artiklarna beskrivs följande funktioner:
Prestandarekommendationer
Modulus och division av opåverkade heltal har betydligt bättre prestanda än modulus och division av påverkade heltal. Vi rekommenderar att du använder osignerade heltal när det är möjligt.
Se även
              C++ AMP (C++ Accelererad massiv parallellitet)
              Lambda-uttryckssyntax
              Referens (C++ AMP)
              Parallell programmering i den interna kodbloggen