Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Hinweis
C++AMP-Header sind ab Visual Studio 2022, Version 17.0, veraltet.
Wenn alle AMP-Header einbezogen werden, führt dies zu Buildfehlern. Definieren Sie _SILENCE_AMP_DEPRECATION_WARNINGS , bevor Sie AMP-Header einschließen, um die Warnungen zu stillen.
C++ Accelerated Massive Parallelism (C++ AMP) beschleunigt die Ausführung von C++-Code, indem datenparallele Hardware wie ein Grafikprozessor (GPU) auf einer separaten Grafikkarte genutzt wird. Mithilfe von C++ AMP können Sie mehrdimensionale Datenalgorithmen codieren, sodass die Ausführung mithilfe von Parallelität auf heterogener Hardware beschleunigt werden kann. Das C++AMP-Programmiermodell umfasst mehrdimensionale Arrays, Indizierung, Speicherübertragung, Tiling und eine mathematische Funktionsbibliothek. Sie können C++AMP-Spracherweiterungen verwenden, um zu steuern, wie Daten von der CPU auf die GPU und zurück verschoben werden, damit Sie die Leistung verbessern können.
Systemanforderungen
Windows 7 oder höher
Windows Server 2008 R2 bis Visual Studio 2019.
DirectX 11 Funktionslevel 11.0 oder höher
Für das Debuggen im Software-Emulator ist Windows 8 oder Windows Server 2012 erforderlich. Für das Debuggen auf der Hardware müssen Sie die Treiber für Ihre Grafikkarte installieren. Weitere Informationen finden Sie unter Debuggen von GPU-Code.
Hinweis: AMP wird derzeit auf ARM64 nicht unterstützt.
Einführung
Die folgenden beiden Beispiele veranschaulichen die primären Komponenten von C++AMP. Gehen Sie davon aus, dass Sie die entsprechenden Elemente von zwei eindimensionalen Arrays hinzufügen möchten. Zum Beispiel könnten Sie {1, 2, 3, 4, 5} und {6, 7, 8, 9, 10} hinzufügen, um {7, 9, 11, 13, 15} zu erhalten. Ohne C++ AMP schreiben Sie möglicherweise den folgenden Code, um die Zahlen hinzuzufügen und die Ergebnisse anzuzeigen.
#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";
}
}
Die wichtigen Teile des Codes sind wie folgt:
Daten: Die Daten bestehen aus drei Arrays. Alle haben denselben Rang (eins) und Länge (fünf).
Iteration: Die erste
for-Schleife bietet einen Mechanismus zum Iterieren über die Elemente in den Arrays. Der Code, den Sie ausführen möchten, um die Summen zu berechnen, ist im erstenforBlock enthalten.Index: Die
idxVariable greift auf die einzelnen Elemente der Arrays zu.
Mit C++ AMP können Sie stattdessen den folgenden Code schreiben.
#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";
}
}
Dieselben grundlegenden Elemente sind vorhanden, aber C++-AMP-Konstrukte werden verwendet:
Daten: Sie verwenden C++-Arrays, um drei C++- AMP-array_view-Objekte zu erstellen. Sie geben vier Werte zum Erstellen eines
array_viewObjekts an: die Datenwerte, den Rang, den Elementtyp und die Länge desarray_viewObjekts in jeder Dimension. Der Rang und der Typ werden als Typparameter übergeben. Die Daten und Länge werden als Konstruktorparameter übergeben. In diesem Beispiel ist das C++-Array, das an den Konstruktor übergeben wird, eindimensional. Der Rang und die Länge werden verwendet, um die rechteckige Form der Daten imarray_viewObjekt zu konstruieren, und die Datenwerte werden verwendet, um das Array auszufüllen. Die Laufzeitbibliothek enthält auch die Arrayklasse, die über eine Schnittstelle verfügt, die derarray_viewKlasse ähnelt und weiter unten in diesem Artikel erläutert wird.Iteration: Die parallel_for_each-Funktion (C++ AMP) bietet einen Mechanismus zum Durchlaufen der Datenelemente oder der Berechnungsdomäne. In diesem Beispiel wird die Computedomäne durch
sum.extentangegeben. Der Code, den Sie ausführen möchten, ist in einem Lambda-Ausdruck oder in einer Kernelfunktion enthalten. Diesrestrict(amp)gibt an, dass nur die Teilmenge der C++-Sprache, die C++ AMP beschleunigen kann, verwendet wird.Index: Die Indexklassenvariable , wird mit einem Rang von 1 deklariert,
idxum der Rangfolge desarray_viewObjekts zu entsprechen. Mithilfe des Indexes können Sie auf die einzelnen Elemente derarray_viewObjekte zugreifen.
Gestalten und Indizieren von Daten: Index und Umfang
Sie müssen die Datenwerte definieren und das Shape der Daten deklarieren, bevor Sie den Kernelcode ausführen können. Alle Daten sind als Array (rechteckig) definiert, und Sie können das Array definieren, um eine beliebige Rangfolge (Anzahl der Dimensionen) zu haben. Die Daten können eine beliebige Größe in einer der Dimensionen aufweisen.
index-Klasse
Die Indexklasse gibt eine Position im array- oder array_view-Objekt an, indem sie den Offset vom Ursprung in jeder Dimension in ein Objekt kapselt. Wenn Sie auf eine Position im Array zugreifen, übergeben Sie anstelle einer Liste ganzzahliger Indizes ein index Objekt an den Indizierungsoperator []. Sie können auf die Elemente in jeder Dimension zugreifen, indem Sie den Operator array::operator() oder den operator array_view::operator() verwenden.
Im folgenden Beispiel wird ein eindimensionaler Index erstellt, der das dritte Element in einem eindimensionalen array_view Objekt angibt. Der Index wird verwendet, um das dritte Element im array_view Objekt zu drucken. Die Ausgabe ist 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
Im folgenden Beispiel wird ein zweidimensionaler Index erstellt, der das Element angibt, in dem die Zeile = 1 und die Spalte = 2 in einem zweidimensionalen array_view Objekt angegeben ist. Der erste Parameter im index Konstruktor ist die Zeilenkomponente, und der zweite Parameter ist die Spaltenkomponente. Die Ausgabe ist 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
Im folgenden Beispiel wird ein dreidimensionaler Index erstellt, der das Element angibt, in dem die Tiefe = 0, die Zeile = 1 und die Spalte = 3 in einem dreidimensionalen array_view Objekt angegeben ist. Beachten Sie, dass der erste Parameter die Tiefenkomponente ist, der zweite Parameter die Zeilenkomponente und der dritte Parameter die Spaltenkomponente ist. Die Ausgabe ist 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
extent-Klasse
Die Extent-Klasse gibt die Länge der Daten in jeder Dimension des array- oder array_view-Objekts an. Sie können eine Erweiterung erstellen und verwenden, um ein array-Objekt oder ein array_view-Objekt zu erstellen. Sie können auch den Umfang eines vorhandenen array Objekts oder array_view Objekts abrufen. Im folgenden Beispiel wird die Länge des Umfangs in jeder Dimension eines array_view Objekts gedruckt.
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";
Im folgenden Beispiel wird ein array_view Objekt erstellt, das dieselben Dimensionen wie das Objekt im vorherigen Beispiel aufweist. In diesem Beispiel wird jedoch ein extent Objekt verwendet, anstatt explizite Parameter im array_view Konstruktor zu verwenden.
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";
Übertragung von Daten auf den Beschleuniger: Array und array_view
Zwei Datencontainer zum Übertragen von Daten in den Beschleuniger sind in der Laufzeitbibliothek definiert. Sie sind die Arrayklasse und die array_view Klasse. Die array Klasse ist eine Containerklasse, die beim Erstellen des Objekts eine tiefe Kopie der Daten erstellt. Die array_view Klasse ist eine Wrapperklasse, die die Daten kopiert, wenn die Kernelfunktion auf die Daten zugreift. Wenn die Daten auf dem Quellgerät benötigt werden, werden die Daten wieder kopiert.
array-Klasse
Wenn ein array Objekt erstellt wird, wird eine tiefe Kopie der Daten auf dem Beschleuniger erstellt, wenn Sie einen Konstruktor verwenden, der einen Zeiger auf die Datensammlung enthält. Die Kernfunktion ändert die Kopie auf dem Beschleuniger. Wenn die Ausführung der Kernelfunktion abgeschlossen ist, müssen Sie die Daten wieder in die Quelldatenstruktur kopieren. Im folgenden Beispiel wird jedes Element in einem Vektor mit 10 multipliziert. Nachdem die Kernelfunktion abgeschlossen ist, wird mit vector conversion operator die Daten in das Vektorobjekt zurückkopiert.
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-Klasse
Das array_view Hat fast die gleichen Member wie die array Klasse, aber das zugrunde liegende Verhalten ist nicht identisch. Daten, die an den array_view Konstruktor übergeben werden, werden nicht auf der GPU repliziert, wie sie mit einem array Konstruktor erfolgt. Stattdessen werden die Daten in den Beschleuniger kopiert, wenn die Kernelfunktion ausgeführt wird. Wenn Sie daher zwei array_view Objekte erstellen, die dieselben Daten verwenden, verweisen beide array_view Objekte auf denselben Speicherplatz. In diesem Fall müssen Sie alle Multithread-Zugriffe synchronisieren. Der Hauptvorteil der Verwendung der array_view Klasse besteht darin, dass Daten nur verschoben werden, wenn sie erforderlich sind.
Vergleich von Array und array_view
In der folgenden Tabelle sind die Ähnlichkeiten und Unterschiede zwischen den Klassen array und array_view zusammengefasst.
| Beschreibung | Arrayklasse | array_view Klasse |
|---|---|---|
| Wenn der Rang bestimmt wird | Zur Kompilierungszeit. | Zur Kompilierungszeit. |
| Wenn der Umfang bestimmt wird | Zur Laufzeit. | Zur Laufzeit. |
| Gestalt | Rechteckig | Rechteckig. |
| Datenspeicherung | Ist ein Datencontainer. | Ist ein Datenwrapper. |
| Copy | Explizite und tiefe Kopien bei der Definition. | Implizite Kopie, wenn von der Kernelfunktion darauf zugegriffen wird. |
| Datenabruf | Durch Kopieren der Arraydaten zurück in ein Objekt im CPU-Thread. | Durch direkten Zugriff auf das array_view Objekt oder durch Aufrufen der array_view::synchronize-Methode können Sie weiterhin auf die Daten im ursprünglichen Container zugreifen. |
Freigegebener Speicher mit Array und Array_view
Gemeinsamer Speicher ist Speicher, auf den sowohl die CPU als auch der Beschleuniger zugreifen können. Durch die Verwendung des gemeinsam genutzten Speichers wird der Mehraufwand für das Kopieren von Daten zwischen der CPU und dem Accelerator beseitigt oder erheblich reduziert. Obwohl der Arbeitsspeicher gemeinsam genutzt wird, kann nicht gleichzeitig von der CPU und dem Beschleuniger darauf zugegriffen werden, und dies führt zu einem nicht definierten Verhalten.
array Objekte können verwendet werden, um eine differenzierte Kontrolle über die Verwendung des gemeinsam genutzten Speichers anzugeben, wenn der zugeordnete Beschleuniger dies unterstützt. Ob ein Beschleuniger gemeinsam genutzten Speicher unterstützt, wird durch die Eigenschaft supports_cpu_shared_memory des Beschleunigers bestimmt, die true zurückgibt, wenn der gemeinsame Speicher unterstützt wird. Wenn gemeinsamer Speicher unterstützt wird, wird die standardmäßige access_type Enumeration für Speicherzuweisungen auf dem Beschleuniger durch die default_cpu_access_type Eigenschaft bestimmt. Standardmäßig übernehmen array und array_view Objekte die gleiche access_type wie das primäre zugeordnete accelerator.
Durch das explizite Festlegen der Array::cpu_access_type Data Member-Eigenschaft können Sie eine differenzierte Kontrolle über die Verwendung des gemeinsam genutzten Speichers ausüben, sodass Sie die App basierend auf den Speicherzugriffsmustern der Berechnungskerne auf die Leistungsmerkmale der Hardware optimieren können. Ein array_view reflektiert das gleiche cpu_access_type wie das array, dem es zugeordnet ist; oder, wenn die array_view ohne Datenquelle erstellt wird, spiegelt ihre access_type die Umgebung wider, in der es erstmalig Speicherplatz zuweist. Das heißt, wenn zuerst vom Host (CPU) zugegriffen wird, verhält es sich so, als ob es über eine CPU-Datenquelle erstellt wurde und die access_type des mit der Erfassung accelerator_view assoziierten freigegeben wird; jedoch, wenn zuerst von einem accelerator_view zugegriffen wird, dann verhält es sich so, als ob es über eine auf diesem accelerator_view erstellte array erstellt wurde und die arrayaccess_type teilt.
Im folgenden Codebeispiel wird gezeigt, wie Sie ermitteln, ob die Standardbeschleuniger gemeinsam genutzten Arbeitsspeicher unterstützt, und anschließend mehrere Arrays erstellt, die unterschiedliche cpu_access_type Konfigurationen aufweisen.
#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);
}
Code über Daten ausführen: parallel_for_each
Die parallel_for_each-Funktion definiert den Code, den Sie auf dem Beschleuniger gegen die Daten in einem array- oder array_view-Objekt ausführen können. Betrachten Sie den folgenden Code aus der Einführung dieses Themas.
#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";
}
}
Die parallel_for_each Methode verwendet zwei Argumente, eine Computedomäne und einen Lambda-Ausdruck.
Die Computedomäne ist ein extent Objekt oder ein tiled_extent Objekt, das die Gruppe von Threads definiert, die für die parallele Ausführung erstellt werden sollen. Für jedes Element in der Computedomäne wird ein Thread generiert. In diesem Fall ist das extent Objekt eindimensional und hat fünf Elemente. Daher werden fünf Threads gestartet.
Der Lambda-Ausdruck definiert den Code, der für jeden Thread ausgeführt werden soll. Die Capture-Klausel [=] gibt an, dass der Rumpf des Lambda-Ausdrucks auf alle erfassten Variablen per Wert zugreift, nämlich a, b und sum. In diesem Beispiel erstellt die Parameterliste eine eindimensionale index Variable mit dem Namen idx. Der Wert von idx[0] ist im ersten Thread 0 und erhöht sich in jedem nachfolgenden Thread um eins. Dies restrict(amp) gibt an, dass nur die Teilmenge der C++-Sprache, die C++ AMP beschleunigen kann, verwendet wird. Die Einschränkungen für Funktionen, die den Einschränkungsmodifizierer aufweisen, werden in restrict (C++ AMP) beschrieben. Weitere Informationen finden Sie unter Lambda-Ausdruckssyntax.
Der Lambda-Ausdruck kann den auszuführenden Code enthalten oder eine separate Kernelfunktion aufrufen. Die Kernelfunktion muss den restrict(amp) Modifizierer enthalten. Das folgende Beispiel entspricht dem vorherigen Beispiel, ruft jedoch eine separate Kernelfunktion auf.
#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";
}
}
Beschleunigen von Code: Kacheln und Barrieren
Sie können zusätzliche Beschleunigung durch die Verwendung von Tiling erzielen. Bei der Tilung werden die Threads in gleich große rechteckige Teilmengen oder Kacheln unterteilt. Sie bestimmen die entsprechende Kachelgröße basierend auf Ihrem Dataset und dem Algorithmus, den Sie codieren. Für jeden Thread haben Sie Zugriff auf den globalen Speicherort eines Datenelements relativ zur Gesamtheit array oder array_view, und Zugriff auf den lokalen Speicherort relativ zur Kachel. Die Verwendung des lokalen Indexwerts vereinfacht Ihren Code, da Sie den Code nicht schreiben müssen, um Indexwerte von global in lokal zu übersetzen. Rufen Sie zum Verwenden von Tiling die „extent::tile“-Methode für den Berechnungsbereich in der parallel_for_each Methode auf, und verwenden Sie ein tiled_index-Objekt im Lambda-Ausdruck.
In typischen Anwendungen sind die Elemente in einer Kachel auf irgendeine Weise miteinander verknüpft, und der Code muss auf Werte auf der gesamten Kachel zugreifen und nachverfolgen. Verwenden Sie das tile_static Schlüsselwort und die tile_barrier::wait-Methode, um dies zu erreichen. Eine Variable mit dem Schlüsselwort tile_static weist einen Bereich auf einer gesamten Kachel auf, und für jede Kachel wird eine Instanz der Variablen erstellt. Sie müssen die Synchronisierung des Kachelthreadzugriffs auf die Variable sicherstellen. Die tile_barrier::wait-Methode beendet die Ausführung des aktuellen Threads, bis alle Threads in der Kachel den Aufruf erreicht tile_barrier::waithaben. So können Sie Werte über die Kachel hinweg sammeln, indem Sie tile_static Variablen verwenden. Anschließend können Sie alle Berechnungen abschließen, die Zugriff auf alle Werte erfordern.
Das folgende Diagramm stellt ein zweidimensionales Array von Samplingdaten dar, das in Kacheln angeordnet ist.
Im folgenden Codebeispiel werden die Samplingdaten aus dem vorherigen Diagramm verwendet. Der Code ersetzt jeden Wert in der Kachel durch den Mittelwert der Werte in der Kachel.
// 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
Mathematische Bibliotheken
C++ AMP enthält zwei mathematische Bibliotheken. Die Doppelpräzisionsbibliothek im Concurrency::precise_math Namespace bietet Unterstützung für Doppelpräzisionsfunktionen. Es bietet auch Unterstützung für Einzelpräzisionsfunktionen, obwohl die Unterstützung für Doppelpräzision in der Hardware weiterhin erforderlich ist. Sie entspricht der C99-Spezifikation (ISO/IEC 9899).It conforms to the C99 Specification (ISO/IEC 9899). Der Beschleuniger muss volle Doppelpräzision unterstützen. Sie können ermitteln, ob dies der Fall ist, indem Sie den Wert des accelerator::supports_double_precision Data Member überprüfen. Die schnelle mathematische Bibliothek im Concurrency::fast_math Namespace enthält einen weiteren Satz mathematischer Funktionen. Diese Funktionen, die nur float Operanden unterstützen, werden schneller ausgeführt, sind aber nicht so präzise wie die in der Doppelpräzisions-Mathematikbibliothek. Die Funktionen sind in der <Headerdatei amp_math.h> enthalten, und alle werden mit restrict(amp)deklariert. Die Funktionen in der <cmath-Headerdatei> werden sowohl in den fast_math-Namespace als auch in den precise_math-Namespace importiert. Das restrict Schlüsselwort wird verwendet, um die <cmath-Version> und die C++AMP-Version zu unterscheiden. Der folgende Code berechnet den Logarithmus zur Basis 10 durch die schnelle Methode für jeden Wert im Rechnungsbereich.
#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";
}
}
Grafikbibliothek
C++ AMP enthält eine Grafikbibliothek, die für die beschleunigte Grafikprogrammierung entwickelt wurde. Diese Bibliothek wird nur auf Geräten verwendet, die systemeigene Grafikfunktionen unterstützen. Die Methoden befinden sich im Concurrency::graphics-Namespace und sind in der <Headerdatei amp_graphics.h> enthalten. Die wichtigsten Komponenten der Grafikbibliothek sind:
Texturklasse: Sie können die Texturklasse verwenden, um Texturen aus dem Arbeitsspeicher oder aus einer Datei zu erstellen. Texturen ähneln Arrays, da sie Daten enthalten, und sie ähneln Containern in der C++-Standardbibliothek hinsichtlich der Zuweisung und Kopierkonstruktion. Weitere Informationen finden Sie unter C++-Standardbibliothekcontainer. Die Vorlagenparameter für die
textureKlasse sind der Elementtyp und der Rang. Der Rang kann 1, 2 oder 3 sein. Der Elementtyp kann einer der kurzen Vektortypen sein, die weiter unten in diesem Artikel beschrieben werden.writeonly_texture_view Klasse: Bietet schreibgeschützten Zugriff auf jede Textur.
Short Vector Library: Definiert eine Reihe von kurzen Vektortypen der Länge 2, 3 und 4, die auf
int, ,uint, ,float,doublenorm oder unorm basieren.
Apps für die universelle Windows-Plattform (UWP)
Wie andere C++-Bibliotheken können Sie C++ AMP in Ihren UWP-Apps verwenden. In diesen Artikeln wird beschrieben, wie Sie C++-AMP-Code in Apps einschließen, die mit C++, C#, Visual Basic oder JavaScript erstellt werden:
C++ AMP und Parallelitätsvisualisierer
Der Konkurrenzvisualisierer bietet Unterstützung zur Leistungsanalyse von C++-AMP-Code. In diesen Artikeln werden die folgenden Features beschrieben:
Analyse von C++ AMP-Code mit dem Konkurrenz-Visualizer
Leistungsempfehlungen
Modul und Division von nicht signierten ganzzahlen weisen eine deutlich bessere Leistung auf als Modul und Division signierter Ganzzahlen. Es wird empfohlen, nach Möglichkeit nicht signierte ganze Zahlen zu verwenden.
Siehe auch
C++ AMP (C++-Beschleunigter massiver Parallelismus)
Lambda-Ausdruckssyntax
Referenz (C++ AMP)
Parallele Programmierung im Blog "Native Code"