Cenni preliminari su C++ AMP
Accelerated Massive Parallelism C++ (AMP C++) accelera l'esecuzione del codice C++ sfruttando hardware parallelo, come un'unità di elaborazione grafica (GPU) in una scheda grafica discreta. Tramite AMP C++, è possibile codificare algoritmi di dati multidimensionali in modo che sia possibile accelerare l'esecuzione tramite il parallelismo su hardware eterogeneo. Il modello di programmazione AMP C++ supporta matrici multidimensionali, indicizzazione, trasferimento di memoria, sezionamento e una libreria di funzioni matematiche. È possibile utilizzare le estensioni del linguaggio C++ AMP per controllare il modo in cui i dati vengono spostati dalla CPU alla GPU e viceversa, al fine di migliorare le prestazioni.
Requisiti di sistema
Windows 7, Windows 8, Windows Server 2008 R2 o Windows Server 2012
Livello funzionalità hardware DirectX 11.0 o successivo
Per eseguire il debug sull'emulatore del software, è richiesto Windows 8 o Windows Server 2012. Per eseguire il debug su hardware, è necessario installare i driver per la scheda grafica. Per ulteriori informazioni, vedere Debug del codice GPU.
Introduzione
I due esempi seguenti illustrano i componenti principali di C++ AMP. Si supponga di voler sommare gli elementi corrispondenti di due matrici unidimensionali. Ad esempio, si vogliono sommare {1, 2, 3, 4, 5} e {6, 7, 8, 9, 10} per ottenere {7, 9, 11, 13, 15}. Senza l'utilizzo di 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";
}
}
Le parti principali del codice sono le seguenti:
Dati: I dati sono costituiti da tre matrici. Tutti hanno lo stesso numero di dimensioni (uno) e lunghezza (cinque).
Iterazione: Il primo ciclo for fornisce un meccanismo per la scorrere gli elementi nelle matrici. Il codice da eseguire per calcolare le somme è contenuto nel primo blocco for.
Indice: La variabile idx accede ai singoli elementi delle matrici.
Mediante C++ AMP, è invece possibile scrivere 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";
}
}
Gli stessi elementi base sono presenti, ma vengono utilizzati i costrutti C++ AMP:
Dati: Si utilizzano matrici C++ per creare tre oggetti AMP C++ array_view. Per creare un oggetto devono essere forniti quattro valori array_view : i valori dei dati, il numero di dimensioni, il tipo di elemento e la lunghezza dell'oggetto array_view in ciascuna dimensione. Il numero di dimensioni e 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 numero di dimensioni e lunghezza vengono utilizzati per costruire la forma rettangolare dei dati nell'oggetto array_view e i valori dei dati vengono utilizzati per riempire la matrice. La libreria di runtime include inoltre Classe array, che dispone di un'interfaccia simile alla classe array_view e viene discussa più avanti in questo articolo.
Iterazione: Funzione parallel_for_each (C++ AMP) fornisce un meccanismo per scorrere i dati, o il dominio di calcolo. In questo esempio, il dominio di calcolo è specificato da sum.extent. Il codice da eseguire è contenuto in un'espressione lambda, o funzione del kernel. restrict(amp) indica che viene utilizzato solo un sottoset del linguaggio C++ AMP può velocizzare.
Indice: La variabile Classe index, idx, è dichiarata con un numero di dimensioni pari a uno per adattarsi al numero di dimensioni dell'oggetto array_view. Tramite l'indice, è possibile accedere ai singoli elementi degli oggetti array_view.
Definizione e indicizzazione dei dati: indice ed extent
È necessario definire valori dei dati e dichiararne la forma dei dati prima di eseguire il codice kernel. Tutti i dati vengono definiti come una matrice (rettangolare), ed è possibile definire la matrice per avere qualsiasi numero di dimensioni. I dati possono essere di qualsiasi dimensione in una qualsiasi delle dimensioni.
Classe index
La Classe index specifica una posizione nell'oggetto array_view o array incapsulando l'offset dall'origine in ciascuna dimensione in un oggetto. Quando si accede a una posizione nella matrice, si passa un oggetto index all'operatore di indicizzazione, [], anziché un elenco di indici di valori interi. È possibile accedere agli elementi in ogni dimensione utilizzando Operatore array::operator() o Operatore array_view::operator().
L'esempio seguente consente di creare 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
L'esempio seguente consente di creare 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 costruttore index è la componente riga e il secondo parametro è la componente 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
L'esempio seguente consente di creare 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 è la componente profondità, il secondo parametro è la componente riga e il terzo parametro è la componente 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 (C++ AMP) specifica la lunghezza dei dati in ciascuna dimensione dell'oggetto array_view o array. È possibile creare un extent e utilizzarlo per creare un oggetto array_view o array. È inoltre possibile recuperare l'extent di un oggetto array o array_view esistente. Nell'esempio seguente viene stampato la lunghezza dell'extent in ogni dimensione di un oggetto 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";
Nell'esempio seguente viene creato un oggetto array_view con le stesse dimensioni dell'oggetto nell'esempio precedente, ma questo esempio utilizza un oggetto extent anziché utilizzare i parametri espliciti nel costruttore array_view.
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";
Spostando i dati all'acceleratore: array e array_view
Due contenitori di dati utilizzati per spostare i dati nell'acceleratore sono definiti nella libreria di runtime. Essi sono Classe array e Classe array_view. La classe array è una classe contenitore che crea una copia completa dei dati quando l'oggetto viene costruito. La classe array_view è una classe wrapper che copia i dati quando la funzione del kernel accede ai dati stessi. Quando i dati sono necessari nel dispositivo di origine, vengono copiati nuovamente.
Classe array
Quando un oggetto array viene costruito, una copia completa dei dati viene creata sull'acceleratore se si utilizza un costruttore che include un puntatore al set di dati. La funzione del kernel modifica la copia sull'acceleratore. Quando l'esecuzione della funzione del kernel è terminata, è necessario copiare i dati nella struttura dati di origine. Nell'esempio seguente si moltiplica per 10 ciascun elemento in un vettore. Dopo che la funzione del kernel è terminata, l' operatore di conversione vettoriale viene utilizzato per copiare i dati nell'oggetto vettoriale.
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
L' array_view ha i membri pressoché uguali alla classe array, ma il comportamento è differente. I dati passati al costruttore array_view non vengono replicati nella GPU come invece avviene con il costruttore array. Invece, i dati vengono copiati nell'acceleratore quando la funzione del kernel viene eseguita. Pertanto, se si creano due oggetti array_view che utilizzano gli stessi dati, entrambi gli oggetti array_view fanno riferimento allo stesso spazio di memoria. In questo caso, è necessario sincronizzare qualsiasi accesso multithreading. Il vantaggio principale dell'utilizzo della classe array_view è che i dati vengono spostati solo se necessario.
Confronto tra array e array_view
Nella tabella seguente sono riepilogate le analogie e le differenze tra le classi array e array_view.
Descrizione |
classe array |
classe array_view |
---|---|---|
Quando il numero di dimensioni viene determinato |
In fase di compilazione. |
In fase di compilazione. |
Quando l'extent viene determinato |
In fase di esecuzione. |
In fase di esecuzione. |
Forma |
Rettangolare. |
Rettangolare. |
Archivio dati |
È un contenitore di dati. |
È un wrapper di dati. |
Copiare |
Esplicita e copia completa al momento della definizione. |
Copia implicita quando vi si accede dalla funzione del kernel. |
Recupero di dati |
Copiando i dati della matrice su un oggetto nel thread della CPU. |
Dall'accesso diretto all'oggetto array_view o chiamando Metodo array_view::synchronize per continuare ad accedere ai dati nel contenitore originale. |
Memoria condivisa con array e array_view
La memoria condivisa è la memoria a cui è possibile accedere sia dalla CPU che dal tasto di scelta rapida. L'utilizzo della memoria condivisa elimina o riduce notevolmente il sovraccarico della copia di dati tra la CPU e l'acceleratore. Sebbene la memoria venga condivisa, l'accesso ad essa non può essere eseguito contemporaneamente sia dalla CPU che dal tasto di scelta rapida. L'accesso simultaneo genera un comportamento indefinito.
È possibile utilizzare gli oggettiarray per specificare un controllo accurato sull'utilizzo della memoria condivisa se il tasto di scelta rapida associato supporta tale controllo. Il fatto che un tasto di scelta rapida supporti la memoria condivisa è determinato dalla proprietà supports_cpu_shared_memory del tasto di scelta rapida che restituisce true quando la memoria condivisa è supportata. Se è supportata la memoria condivisa, il valore Enumerazione access_type predefinito per le allocazioni di memoria nell'acceleratore è determinato dalla proprietà default_cpu_access_type. Per impostazione predefinita, gli oggetti array e array_view hanno lo stesso access_type dell'oggetto primario accelerator associato.
Impostando la proprietà Membro dati array::cpu_access_type array in modo esplicito, è possibile esercitare un controllo preciso su come la memoria condivisa viene utilizzata, che consente di ottimizzare l'applicazione per le caratteristiche di prestazione dell'hardware, in base ai modelli di accesso alla memoria del relativo kernel di calcolo. array_view riflette lo stesso cpu_access_type di array a cui è associato; in alternativa, se array_view viene costruito senza un'origine dati, il relativo access_type riflette l'ambiente che per primo ne determina l'allocazione di memoria. Ovvero, se l'accesso all'oggetto avviene prima dall'host (CPU), l'oggetto si comporta come se fosse stato creato da un'origine dati di CPU e condivide access_type dell'oggetto accelerator_view associato dall'acquisizione; tuttavia, se l'accesso all'oggetto avviene da un oggetto accelerator_view, quindi l'oggetto si comporta come se fosse stato creato su un oggetto array creato su accelerator_view e condivide l'oggetto access_type di array.
Nell'esempio di codice riportato di seguito viene illustrato come determinare se il tasto di scelta rapida predefinito supporta la memoria condivisa, quindi vengono create varie matrici con configurazioni di cpu_access_type differenti.
#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);
}
Codice in esecuzione sui dati: parallel_for_each
La funzione parallel_for_each definisce il codice da eseguire nell'acceleratore sui dati negli oggetti array_view o array. Si consideri il seguente codice dell'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 metodo parallel_for_each accetta due argomenti, il dominio di calcolo e un'espressione lambda.
Il dominio di calcolo è un oggetto extent o un oggetto tiled_extent che definisce il set di thread da creare per l'esecuzione parallela. Un thread viene generato per ogni elemento nel dominio di calcolo. In questo caso, l'oggetto extent è unidimensionale e ha cinque elementi. Di conseguenza, vengono avviati cinque thread.
L' espressione lambda definisce il codice da eseguire su ogni thread. La clausola di acquisizione, [=], 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 index denominata idx. Il valore di idx[0] è 0 nel primo thread e aumenta di uno in ciascun thread successivo. restrict(amp) indica che viene utilizzato solo un sottoset del linguaggio C++ AMP può velocizzare. Le limitazioni delle funzioni che hanno il modificatore restrict sono descritte in Clausola di restrizione (AMP C++). Per ulteriori informazioni, vedere Sintassi delle espressioni lambda.
L'espressione lambda può includere il codice da eseguire o può chiamare una funzione del kernel separata. La funzione del kernel deve includere il modificatore restrict(amp). L'esempio seguente è equivalente a quello precedente, ma chiama una funzione del 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";
}
}
Codice accelerato: sezioni e barriere
È possibile ottenere un'accelerazione aggiuntiva utilizzando l'affiancamento. L'affiancamento divide i thread in sottoinsiemi rettangolari o sezioni uguali. È possibile determinare la dimensione corretta della sezione in base al set di dati e all'algoritmo che si sta codificando. Per ogni thread, è possibile accedere al percorso globale di un elemento relativo all'intero array o array_view e accedere al percorso locale rispetto alla sezione. L'utilizzo del valore locale dell'indice semplifica il codice in quanto non è necessario scrivere il codice per convertire i valori dell'indice da globale a locale. Per utilizzare l'affiancamento, chiamare Metodo extent::tile nel dominio di calcolo del metodo parallel_for_each e utilizzare un oggetto tiled_index nell'espressione lambda.
Nelle applicazioni tipiche, gli elementi in una sezione sono correlati in qualche modo, e il codice deve accedere e tenere traccia dei valori nella sezione. Utilizzare la parola chiave parola chiave tile_static e Metodo tile_barrier::wait per tale scopo. Una variabile con la parola chiave tile_static ha un ambito in un'intera sezione, e un'istanza della variabile viene creata per ogni sezione. È necessario gestire la sincronizzazione dell'accesso ai thread di sezione alla variabile. La Metodo tile_barrier::wait arresta l'esecuzione del thread corrente finché tutti i thread nella sezione non hanno raggiunto la chiamata a tile_barrier::wait. Pertanto è possibile accumulare valori nella sezione utilizzando variabili tile_static. È 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 sezioni.
Nell'esempio di codice seguente vengono utilizzati i dati di campionamento dal diagramma precedente. Il codice sostituisce ogni valore nella sezione con la media dei valori nella sezione.
// 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
AMP C++ include due librerie matematiche. La libreria della precisione doppia in Spazio dei nomi Concurrency::precise_math fornisce il supporto per le funzioni a precisione doppia. Fornisce inoltre il supporto per funzioni a precisione singola, sebbene il supporto alla precisione doppia sull'hardware sia ancora necessario. È conforme alla specifica C99 (ISO/IEC 9899). L'acceleratore deve supportare la precisione doppia completa. È possibile determinare se esegue l'operazione controllando il valore di Membro dati accelerator::supports_double_precision. La libreria del calcolo veloce, in Spazio dei nomi Concurrency::fast_math, contiene un altro set di funzioni matematiche. Queste funzioni, che supportano solo operandi float, si eseguono più rapidamente ma non sono precise come quelle nella libreria del calcolo a precisione doppia. Le funzioni sono contenute nel file di intestazione <amp_math.h> e sono tutte dichiarate con restrict(amp). Le funzioni nel file di intestazione <cmath> vengono importate negli spazi dei nomi fast_math e precise_math. La parola chiave restrict viene utilizzata per distinguere la versione <cmath> e la versione C++ AMP. Nel codice seguente viene calcolato il logaritmo in base 10, utilizzando il metodo veloce, di ciascun valore che si trova 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(logs[idx]);
}
);
for (int i = 0; i < 6; i++) {
std::cout << logs[i] << "\n";
}
}
Libreria grafica
AMP C++ include una libreria grafica progettata per la programmazione grafica accelerata. Questa libreria viene utilizzata solo in dispositivi che supportano le funzionalità grafiche native. I metodi sono in Spazio dei nomi Concurrency::graphics e sono contenuti nel file di intestazione <amp_graphics.h>. I componenti principali della libreria grafica sono:
Classe texture: È possibile utilizzare la classe texture per creare trame dalla memoria o da un file. Le trame sono simili alle matrici perché contengono dati, e sono simili ai contenitori nella Standard Template Library (STL) rispetto alla costruzione dell'assegnamento e della copia. Per ulteriori informazioni, vedere Contenitori STL. I parametri del modello per la classe texture sono il tipo di elemento e il numero di dimensioni. Il numero di dimensioni può essere 1, 2 o 3. Il tipo di elemento può essere uno dei tipi di vettori short che vengono descritti più avanti in questo articolo.
Classe writeonly_texture_view: fornisce accesso in sola scrittura a qualsiasi trama.
Breve raccolta vettoriale: Definisce un set di tipi vettore short di lunghezza 2, 3 e 4 basati su int, uint, float, double, norma, o unorm.
Applicazioni Windows Store
Come altre librerie di C++, è possibile utilizzare AMP C++ nelle applicazioni Windows Store. In questi articoli viene descritto come includere il codice C++ AMP nelle applicazioni create utilizzando C++, C#, Visual Basic o JavaScript:
AMP C++ e il visualizzatore di concorrenze
Il visualizzatore di concorrenze include il supporto per analizzare le prestazioni del codice AMP C++. In questi articoli vengono descritte le seguenti funzionalità:
Suggerimenti relativi alle prestazioni
Il modulo e la divisione di interi senza segno hanno prestazioni significativamente migliori che il modulo e la divisione di interi con segno. È consigliabile utilizzare gli interi senza segno quando possibile.
Vedere anche
Riferimenti
Sintassi delle espressioni lambda