Cenni preliminari su C++ AMP
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.
C++ Accelerated Massive Parallelism (C++ AMP) accelera l'esecuzione del codice C++ sfruttando l'hardware parallelo dei dati, ad esempio un'unità di elaborazione grafica (GPU) in una scheda grafica discreta. Usando C++ AMP, è possibile codificare algoritmi di dati multidimensionali in modo che l'esecuzione possa essere accelerata usando il parallelismo su hardware eterogeneo. Il modello di programmazione C++ AMP include le matrici multidimensionali, l'indicizzazione, il trasferimento di memoria, l'affiancamento e una libreria di funzioni matematiche. È possibile usare le estensioni del linguaggio C++ AMP per controllare il modo in cui i dati vengono spostati dalla CPU alla GPU e indietro, in modo da migliorare le prestazioni.
Requisiti di sistema
Windows 7 o versione successiva
Da Windows Server 2008 R2 a Visual Studio 2019.
Hardware DirectX 11 Feature Level 11.0 o versione successiva
Per il debug nell'emulatore software, è necessario Windows 8 o Windows Server 2012. Per eseguire il debug nell'hardware, è necessario installare i driver per la scheda grafica. Per altre informazioni, vedere Debug del codice GPU.
Nota: AMP non è attualmente supportato in ARM64.
Introduzione
I due esempi seguenti illustrano i componenti principali di C++ AMP. Si supponga di voler aggiungere gli elementi corrispondenti di due matrici unidimensionali. Ad esempio, è possibile aggiungere {1, 2, 3, 4, 5}
e {6, 7, 8, 9, 10}
ottenere {7, 9, 11, 13, 15}
. Senza usare C++ AMP, è possibile scrivere il codice seguente per aggiungere i numeri e visualizzare i risultati.
#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";
}
}
Di seguito sono riportate le parti importanti del codice:
Dati: i dati sono costituiti da tre matrici. Tutti hanno lo stesso rango (uno) e lunghezza (cinque).
Iterazione: il primo
for
ciclo fornisce un meccanismo per l'iterazione degli elementi nelle matrici. Il codice da eseguire per calcolare le somme è contenuto nel primofor
blocco.Indice: la
idx
variabile accede ai singoli elementi delle matrici.
Usando C++ AMP, è possibile scrivere invece il codice seguente.
#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";
}
}
Sono presenti gli stessi elementi di base, ma vengono usati costrutti AMP C++:
Dati: si usano matrici C++ per costruire tre oggetti C++ AMP array_view . Si specificano quattro valori per costruire un
array_view
oggetto: i valori dei dati, la classificazione, il tipo di elemento e la lunghezza dell'oggettoarray_view
in ogni dimensione. Il rango e il tipo vengono passati come parametri di tipo. I dati e la lunghezza vengono passati come parametri del costruttore. In questo esempio, la matrice C++ passata al costruttore è unidimensionale. Il rango e la lunghezza vengono usati per costruire la forma rettangolare dei dati nell'oggettoarray_view
e i valori dei dati vengono usati per riempire la matrice. La libreria di runtime include anche la classe array, che ha un'interfaccia simile allaarray_view
classe e viene descritta più avanti in questo articolo.Iterazione: la funzione parallel_for_each (C++ AMP) fornisce un meccanismo per l'iterazione degli elementi dati o del dominio di calcolo. In questo esempio il dominio di calcolo viene specificato da
sum.extent
. Il codice da eseguire è contenuto in un'espressione lambda o in una funzione kernel. Indicarestrict(amp)
che viene usato solo il subset del linguaggio C++ che C++ AMP può accelerare.Index: la variabile index Class ,
idx
, viene dichiarata con un rango di uno in modo che corrisponda alla classificazione dell'oggettoarray_view
. Usando l'indice, è possibile accedere ai singoli elementi degliarray_view
oggetti .
Data shaping e indicizzazione: indice ed extent
È necessario definire i valori dei dati e dichiarare la forma dei dati prima di poter eseguire il codice del kernel. Tutti i dati vengono definiti come una matrice (rettangolare) ed è possibile definire la matrice in modo che abbia qualsiasi rango (numero di dimensioni). I dati possono essere di qualsiasi dimensione in qualsiasi dimensione.
Classe index
La classe index specifica una posizione nell'oggetto array
o array_view
incapsulando l'offset dall'origine in ogni dimensione in un oggetto . Quando si accede a una posizione nella matrice, si passa un index
oggetto all'operatore di indicizzazione, []
, anziché a un elenco di indici integer. È possibile accedere agli elementi in ogni dimensione usando l'operatore array::operator() o l'operatore array_view::operator().
Nell'esempio seguente viene creato un indice unidimensionale che specifica il terzo elemento in un oggetto unidimensionale array_view
. L'indice viene utilizzato per stampare il terzo elemento nell'oggetto array_view
. L'output è 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
Nell'esempio seguente viene creato un indice bidimensionale che specifica l'elemento in cui la riga = 1 e la colonna = 2 in un oggetto bidimensionale array_view
. Il primo parametro nel index
costruttore è il componente di riga e il secondo parametro è il componente della colonna. L'output è 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
Nell'esempio seguente viene creato un indice tridimensionale che specifica l'elemento in cui la profondità = 0, la riga = 1 e la colonna = 3 in un oggetto tridimensionale array_view
. Si noti che il primo parametro è il componente depth, il secondo parametro è il componente riga e il terzo parametro è il componente della colonna. L'output è 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
Classe extent
La classe extent specifica la lunghezza dei dati in ogni dimensione dell'oggetto array
o array_view
. È possibile creare un extent e usarlo per creare un array
oggetto o array_view
. È anche possibile recuperare l'extent di un oggetto o array_view
esistentearray
. Nell'esempio seguente viene stampata la lunghezza dell'extent in ogni dimensione di un array_view
oggetto .
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";
Nell'esempio seguente viene creato un array_view
oggetto con le stesse dimensioni dell'oggetto nell'esempio precedente, ma in questo esempio viene utilizzato un extent
oggetto anziché utilizzare parametri espliciti nel array_view
costruttore.
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";
Spostamento di dati nell'acceleratore: matrice e array_view
Nella libreria di runtime vengono definiti due contenitori di dati usati per spostare i dati nell'acceleratore. Sono la classe della matrice e la classe array_view. La array
classe è una classe contenitore che crea una copia completa dei dati quando viene costruito l'oggetto. La array_view
classe è una classe wrapper che copia i dati quando la funzione kernel accede ai dati. Quando i dati sono necessari nel dispositivo di origine, i dati sono stati copiati di nuovo.
Classe array
Quando viene costruito un array
oggetto, viene creata una copia completa dei dati sull'acceleratore se si usa un costruttore che include un puntatore al set di dati. La funzione kernel modifica la copia sull'acceleratore. Al termine dell'esecuzione della funzione kernel, è necessario copiare nuovamente i dati nella struttura dei dati di origine. L'esempio seguente moltiplica ogni elemento in un vettore per 10. Al termine della funzione kernel, vector conversion operator
viene usato per copiare nuovamente i dati nell'oggetto vettore.
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";
}
Classe array_view
ha array_view
quasi gli stessi membri della array
classe , ma il comportamento sottostante non è lo stesso. I dati passati al array_view
costruttore non vengono replicati nella GPU perché si tratta di un array
costruttore. I dati vengono invece copiati nell'acceleratore quando viene eseguita la funzione kernel. Pertanto, se si creano due array_view
oggetti che usano gli stessi dati, entrambi array_view
gli oggetti fanno riferimento allo stesso spazio di memoria. Quando si esegue questa operazione, è necessario sincronizzare qualsiasi accesso multithreading. Il vantaggio principale dell'uso della classe è che i array_view
dati vengono spostati solo se necessario.
Confronto tra matrici e array_view
La tabella seguente riepiloga le analogie e le differenze tra le array
classi e array_view
.
Descrizione | array (classe) | array_view (classe) |
---|---|---|
Quando viene determinato il rango | In fase di compilazione. | In fase di compilazione. |
Quando viene determinato l'extent | In fase di esecuzione. | In fase di esecuzione. |
Forma | Rettangolare. | Rettangolare. |
Archiviazione di dati | Contenitore di dati. | Wrapper dati. |
Copia | Copia esplicita e completa in corrispondenza della definizione. | Copia implicita quando viene eseguito l'accesso dalla funzione kernel. |
Recupero dati | Copiando nuovamente i dati della matrice in un oggetto nel thread della CPU. | Per accedere direttamente all'oggetto array_view o chiamando il metodo array_view::synchronize per continuare ad accedere ai dati nel contenitore originale. |
Memoria condivisa con matrice e array_view
La memoria condivisa è la memoria a cui è possibile accedere sia la CPU che l'acceleratore. L'uso della memoria condivisa elimina o riduce significativamente il sovraccarico di copia dei dati tra la CPU e l'acceleratore. Anche se la memoria è condivisa, non è possibile accedervi contemporaneamente sia dalla CPU che dall'acceleratore e in questo modo causa un comportamento indefinito.
array
Gli oggetti possono essere usati per specificare un controllo con granularità fine sull'uso della memoria condivisa se l'acceleratore associato lo supporta. Se un acceleratore supporta la memoria condivisa è determinato dalla proprietà supports_cpu_shared_memory dell'acceleratore, che restituisce true
quando è supportata la memoria condivisa. Se la memoria condivisa è supportata, l'enumerazione predefinita access_type per le allocazioni di memoria sull'acceleratore viene determinata dalla default_cpu_access_type
proprietà . Per impostazione predefinita, array
e array_view
gli oggetti assumono lo stesso access_type
valore dell'oggetto primario associato.accelerator
Impostando la proprietà array::cpu_access_type Data Member di un oggetto array
in modo esplicito, è possibile esercitare un controllo granulare sulla modalità di utilizzo della memoria condivisa, in modo da poter ottimizzare l'app per le caratteristiche di prestazioni dell'hardware, in base ai modelli di accesso alla memoria dei kernel di calcolo. Un array_view
oggetto riflette lo stesso cpu_access_type
elemento array
a cui è associato oppure, se il array_view viene costruito senza un'origine dati, access_type
riflette l'ambiente che lo causa per primo l'allocazione dell'archiviazione. Ciò significa che, se l'accesso viene eseguito per la prima volta dall'host (CPU), si comporta come se fosse stato creato tramite un'origine dati della CPU e condivide l'oggetto access_type
dell'oggetto associato dall'acquisizioneaccelerator_view
, tuttavia, se è stato eseguito per la prima volta da un accelerator_view
oggetto , si comporta come se fosse stato creato su tale accelerator_view
array
oggetto e condivide l'oggetto array
dell'oggetto access_type
.
Nell'esempio di codice seguente viene illustrato come determinare se l'acceleratore predefinito supporta la memoria condivisa e quindi crea diverse matrici con configurazioni cpu_access_type diverse.
#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);
}
Esecuzione di codice sui dati: parallel_for_each
La funzione parallel_for_each definisce il codice che si vuole eseguire sull'acceleratore sui dati nell'oggetto array
o array_view
. Si consideri il codice seguente dall'introduzione di questo argomento.
#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";
}
}
Il parallel_for_each
metodo accetta due argomenti, un dominio di calcolo e un'espressione lambda.
Il dominio di calcolo è un extent
oggetto o un tiled_extent
oggetto che definisce il set di thread da creare per l'esecuzione parallela. Viene generato un thread per ogni elemento nel dominio di calcolo. In questo caso, l'oggetto extent
è unidimensionale e ha cinque elementi. Pertanto, vengono avviati cinque thread.
L'espressione lambda definisce il codice da eseguire in ogni thread. La clausola capture, [=]
, specifica che il corpo dell'espressione lambda accede a tutte le variabili acquisite per valore, che in questo caso sono a
, b
e sum
. In questo esempio l'elenco di parametri crea una variabile unidimensionale denominata index
idx
. Il valore di idx[0]
è 0 nel primo thread e aumenta di uno in ogni thread successivo. Indica restrict(amp)
che viene usato solo il subset del linguaggio C++ che C++ AMP può accelerare. Le limitazioni per le funzioni che hanno il modificatore restrict sono descritte in Restrict (C++ AMP). Per altre informazioni, vedere sintassi delle espressioni lambda.
L'espressione lambda può includere il codice da eseguire oppure può chiamare una funzione kernel separata. La funzione kernel deve includere il restrict(amp)
modificatore. L'esempio seguente equivale all'esempio precedente, ma chiama una funzione kernel separata.
#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";
}
}
Accelerazione del codice: riquadri e barriere
È possibile ottenere un'accelerazione aggiuntiva usando la tiling. L'affiancamento divide i thread in sottoinsiemi rettangolari o riquadri uguali. Si determinano le dimensioni appropriate del riquadro in base al set di dati e all'algoritmo che si stanno codificando. Per ogni thread, è possibile accedere alla posizione globale di un elemento dati relativo all'intero array
o array_view
e all'accesso alla posizione locale rispetto al riquadro. L'uso del valore dell'indice locale semplifica il codice perché non è necessario scrivere il codice per convertire i valori di indice da globale a locale. Per usare l'associazione, chiamare il metodo extent::tile nel dominio di calcolo nel parallel_for_each
metodo e usare un oggetto tiled_index nell'espressione lambda.
Nelle applicazioni tipiche, gli elementi di un riquadro sono correlati in qualche modo e il codice deve accedere e tenere traccia dei valori nel riquadro. Usare la parola chiave tile_static e il metodo tile_barrier::wait per eseguire questa operazione. Una variabile con la parola chiave tile_static ha un ambito in un intero riquadro e viene creata un'istanza della variabile per ogni riquadro. È necessario gestire la sincronizzazione dell'accesso a thread di riquadri alla variabile. Il metodo tile_barrier::wait interrompe l'esecuzione del thread corrente finché tutti i thread nel riquadro non hanno raggiunto la chiamata a tile_barrier::wait
. È quindi possibile accumulare valori nel riquadro usando tile_static variabili. È quindi possibile completare tutti i calcoli che richiedono l'accesso a tutti i valori.
Il diagramma seguente rappresenta una matrice bidimensionale di dati di campionamento disposti in riquadri.
Nell'esempio di codice seguente vengono usati i dati di campionamento del diagramma precedente. Il codice sostituisce ogni valore nel riquadro in base alla media dei valori nel riquadro.
// 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
Librerie matematiche
C++ AMP include due librerie matematiche. La libreria a precisione doppia nello spazio dei nomi Concurrency::p recise_math fornisce il supporto per le funzioni a precisione doppia. Fornisce anche il supporto per funzioni a precisione singola, anche se è ancora necessario il supporto a precisione doppia nell'hardware. È conforme alla specifica C99 (ISO/IEC 9899). L'acceleratore deve supportare la precisione doppia completa. È possibile determinare se esegue questa operazione controllando il valore del membro dati accelerator::supports_double_precision. La libreria matematica veloce, nello spazio dei nomi Concurrency::fast_math, contiene un altro set di funzioni matematiche. Queste funzioni, che supportano solo float
gli operandi, vengono eseguite più rapidamente, ma non sono precise come quelle nella libreria matematica a precisione doppia. Le funzioni sono contenute nel <file di intestazione amp_math.h> e tutte sono dichiarate con restrict(amp)
. Le funzioni nel <file di intestazione cmath> vengono importate sia negli spazi dei fast_math
nomi che precise_math
in . La restrict
parola chiave viene usata per distinguere la <versione cmath> e la versione C++ AMP. Il codice seguente calcola il logaritmo di base 10, usando il metodo rapido, di ogni valore presente nel dominio di calcolo.
#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";
}
}
Libreria grafica
C++ AMP include una libreria grafica progettata per la programmazione grafica accelerata. Questa libreria viene usata solo nei dispositivi che supportano la funzionalità grafica nativa. I metodi si trovano nello spazio dei nomi Concurrency::graphics e sono contenuti nel <file di intestazione amp_graphics.h>. I componenti chiave della libreria grafica sono:
classe texture: è possibile usare la classe trama per creare trame dalla memoria o da un file. Le trame sono simili a matrici perché contengono dati e sono simili ai contenitori nella libreria standard C++ rispetto all'assegnazione e alla costruzione della copia. Per altre informazioni, vedere C++ Standard Library Containers (Contenitori della libreria standard di C++). I parametri del modello per la
texture
classe sono il tipo di elemento e la classificazione. Il rango può essere 1, 2 o 3. Il tipo di elemento può essere uno dei tipi di vettore brevi descritti più avanti in questo articolo.classe writeonly_texture_view: fornisce l'accesso in sola scrittura a qualsiasi trama.
Libreria di vettori brevi: definisce un set di tipi di vettore brevi di lunghezza 2, 3 e 4 basati su
int
,uint
float
, ,double
, norm o unorm.
App UWP (Universal Windows Platform)
Come altre librerie C++, puoi usare C++ AMP nelle tue app UWP. Questi articoli descrivono come includere il codice C++ AMP nelle app create usando C++, C#, Visual Basic o JavaScript:
Visualizzatore di concorrenza e AMP C++
Il visualizzatore di concorrenza include il supporto per l'analisi delle prestazioni del codice C++ AMP. Questi articoli descrivono queste funzionalità:
Raccomandazioni per le prestazioni
Il modulo e la divisione di interi senza segno offrono prestazioni notevolmente migliori rispetto al modulo e alla divisione di interi con segno. Quando possibile, è consigliabile usare numeri interi senza segno.
Vedi anche
C++ AMP (C++ Accelerated Massive Parallelism)
Sintassi delle espressioni lambda
Riferimento (C++ AMP)
Blog sulla programmazione parallela nel codice nativo