Übersicht über C++ AMP

Hinweis

C++AMP-Header sind ab Visual Studio 2022, Version 17.0, veraltet. Wenn Alle AMP-Header eingeschlossen werden, werden Buildfehler generiert. 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 die Vorteile Daten parallel verarbeitender Hardware, beispielsweise ein Grafikprozessor (Graphics Processing Unit, GPU) auf einer separaten Grafikkarte, genutzt werden. Sie können mithilfe von C++ AMP mehrdimensionale Datenalgorithmen programmieren, sodass die Ausführung durch die parallele Verarbeitung auf heterogener Hardware beschleunigt werden kann. Das C++ AMP-Programmiermodell umfasst mehrdimensionale Arrays, Indizierung, Arbeitsspeicherübertragung, Kacheln und eine Bibliothek mathematischer Funktionen. Sie können C++ AMP-Spracherweiterungen verwenden, um zu steuern, wie Daten von der CPU zur GPU und umgekehrt verschoben werden, sodass Sie die Leistung verbessern können.

Systemanforderungen

  • Windows 7 oder höher

  • Windows Server 2008 R2 bis Visual Studio 2019.

  • DirectX 11-Funktionsebene 11.0 oder aktuellere Hardware

  • 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 Beispielen veranschaulichen die primären Komponenten von C++ AMP. Angenommen, Sie möchten die entsprechenden Elemente zweier eindimensionaler Arrays addieren. Sie können z. B. hinzufügen {1, 2, 3, 4, 5} und {6, 7, 8, 9, 10} abrufen {7, 9, 11, 13, 15}. Ohne C++ AMP zu verwenden, schreiben Sie den folgenden Code, um die Zahlen zu addieren 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 lauten wie folgt:

  • Daten: Die Daten bestehen aus drei Arrays. Alle haben den gleichen Rang (Eins) und die gleiche Länge (Fünf).

  • Iteration: Die erste for-Schleife stellt einen Mechanismus für das Durchlaufen der Elemente in den Arrays bereit. Der Code, der zur Berechnung der Summen ausgeführt werden soll, befindet sich im ersten for-Block.

  • Index: Über die Variable idx wird auf die einzelnen Elemente der Arrays zugegriffen.

Bei Verwendung von 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";
    }
}

Es sind zwar die gleichen grundlegenden Elemente vorhanden, es werden jedoch C++ AMP-Konstrukte verwendet:

  • Daten: Sie verwenden C++-Arrays, um drei C++-AMP-array_view-Objekte zu erstellen. Sie stellen vier Werte zur Verfügung, um ein array_view-Objekt zu erstellen: die Datenwerte, den Rang, den Elementtyp und die Länge des array_view-Objekts in jeder Dimension. Der Rang und der Typ werden als Typparameter übergeben. Die Daten und die Länge werden als Konstruktorparameter übergeben. In diesem Beispiel ist das an den Konstruktor übergebene C++-Array eindimensional. Der Rang und die Länge werden verwendet, um die rechteckige Form der Daten im array_view-Objekt zu erstellen, und die Datenwerte werden verwendet, um das Array zu füllen. Die Laufzeitbibliothek enthält auch die Arrayklasse, die über eine Schnittstelle verfügt, die der array_view Klasse ähnelt und weiter unten in diesem Artikel erläutert wird.

  • Iteration: Die parallel_for_each-Funktion (C++ AMP) stellt einen Mechanismus zum Durchlaufen der Datenelemente oder zum Berechnen von Aufgaben bereit Standard. In diesem Beispiel wird die Compute-Domäne durch sum.extent angegeben. Der Code, den Sie ausführen möchten, ist in einem Lambda-Ausdruck oder in einer Kernelfunktion enthalten. Die Anweisung restrict(amp) gibt an, dass nur die Teilmenge der Programmiersprache C++ verwendet wird, die mit C++ AMP beschleunigt werden kann.

  • Index: Die Indexklassenvariable , wird mit einem Rang von 1 deklariert, idxum der Rangfolge des array_view Objekts zu entsprechen. Mithilfe des Index kann auf die einzelnen Elemente der array_view-Objekte zugegriffen werden.

Strukturieren und Indizieren von Daten: Index und Umfang

Sie müssen die Datenwerte definieren und die Form der Daten deklarieren, bevor Sie den Kernelcode ausführen können. Alle Daten werden als Array (rechteckig) definiert, und Sie können das Array mit einem die oft ausgegebene Befehlszeilen Rang (Anzahl der Dimensionen) definieren. Die Daten können in jeder Dimension von beliebiger Größe sein.

index-Klasse

Die Indexklasse gibt eine Position im array Objekt an array_view , indem der Offset vom Ursprung in jeder Dimension in ein Objekt gekapselt wird. Wenn Sie auf eine Position im Array zugreifen, übergeben Sie ein index-Objekt an den Indizierungsoperator [] statt einer Liste mit ganzzahligen Indizes. 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 auszugeben. Die Ausgabe lautet 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, bei dem die Zeile = 1 und die Spalte = 2 in einem zweidimensionalen array_view-Objekt 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, 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

The extent Class specifies the length of the data in each dimension of the array or array_view object. Sie können einen Wertebereich erstellen und diesen verwenden, um ein array- oder array_view-Objekt zu erstellen. Sie können den Wertebereich eines vorhandenen array- oder array_view-Objekts auch abrufen. Im folgenden Beispiel wird die Länge des Wertebereichs in jeder Dimension eines array_view-Objekts ausgegeben.

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 die gleichen Dimensionen wie das Objekt im vorherigen Beispiel hat, aber in diesem Beispiel wird ein extent-Objekt statt expliziter Parameter im array_view-Konstruktor verwendet.

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";

Verschieben von Daten in die Beschleunigung: array und array_view

In der Laufzeitbibliothek sind zwei Datencontainer definiert, die verwendet werden, um Daten in den Beschleuniger zu verschieben. Sie sind die Arrayklasse und die array_view Klasse. Die array-Klasse ist eine Containerklasse, die eine tiefe Kopie der Daten erstellt, wenn das Objekt erstellt wird. 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 sie zurückkopiert.

array-Klasse

Wenn ein array-Objekt erstellt wird und Sie einen Konstruktor verwenden, der einen Zeiger auf das Dataset enthält, wird im Beschleuniger eine tiefe Kopie der Daten erstellt. Die Kernelfunktion ändert die Kopie im Beschleuniger. Wenn die Ausführung der Kernelfunktion beendet ist, müssen Sie die Daten zurück in die Quelldatenstruktur kopieren. Im folgenden Beispiel wird jedes Element in einem Vektor mit 10 multipliziert. Nachdem die Kernelfunktion abgeschlossen ist, wird die vector conversion operator Daten wieder in das Vektorobjekt kopiert.

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

Die array_view-Klasse verfügt über fast dieselben Member wie die array-Klasse, aber das zugrunde liegende Verhalten ist unterschiedlich. Die Daten, die dem array_view-Konstruktor übergeben werden, werden nicht wie beim array-Konstruktor im Grafikprozessor repliziert. Stattdessen werden die Daten in den Beschleuniger kopiert, wenn die Kernelfunktion ausgeführt wird. Wenn Sie zwei array_view-Objekte erstellen, die dieselben Daten verwenden, verweisen daher beide array_view-Objekte auf denselben Arbeitsspeicherbereich. In diesem Fall müssen Sie jeden Multithreaded-Zugriff synchronisieren. Der Hauptvorteil bei der Verwendung der array_view-Klasse besteht darin, dass Daten nur verschoben werden, wenn es erforderlich ist.

Vergleich von "array" und "array_view"

In der folgenden Tabelle werden die Übereinstimmungen und Unterschiede zwischen der array- und der array_view-Klasse zusammengefasst.

Beschreibung array-Klasse array_view-Klasse
Zeitpunkt der Bestimmung des Rangs Beim Kompilieren Beim Kompilieren
Zeitpunkt der Bestimmung des Wertebereichs Zur Laufzeit Zur Laufzeit
Form Rechteckig Rechteckig
Datenspeicher Ist ein Datencontainer Ist ein Datenwrapper
Kopieren Explizit und tiefe Kopie während der Definition Implizite Kopie, wenn von der Kernelfunktion darauf zugegriffen wird
Abrufen von Daten 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

Freigegebener Arbeitsspeicher ist der Arbeitsspeicher, auf den sowohl die CPU als auch die Zugriffstaste zugreifen kann. Durch die Verwendung von freigegebenem Arbeitsspeicher entfällt bzw. reduziert sich der Mehraufwand zum Kopieren von Daten zwischen CPU und der Zugriffstaste erheblich. Obwohl der Arbeitsspeicher freigegeben wird, kann darauf nicht von CPU und der Zugriffstaste gleichzeitig zugegriffen werden. In diesem Fall kommt es zu einem nicht definierten Verhalten.

Mithilfe von array-Objekten kann die Verwendung von freigegebenem Arbeitsspeicher präzise gesteuert werden, sofern die zugeordnete Zugriffstaste dies unterstützt. Gibt an, ob eine Zugriffstaste gemeinsam genutzten Speicher unterstützt wird, wird durch die supports_cpu_shared_memory-Eigenschaft der Zugriffstaste bestimmt, die beim Unterstützen des gemeinsam genutzten Speichers zurückgegeben true wird. Wenn der gemeinsam genutzte Speicher unterstützt wird, wird die standardmäßige access_type Enumeration für Speicherzuweisungen auf der Zugriffstaste durch die default_cpu_access_type Eigenschaft bestimmt. Standardmäßig wird für array- und array_view-Objekte der gleiche access_type wie für das primäre zugeordnete accelerator-Objekt verwendet.

Durch Festlegen der Array::cpu_access_type Data Member-Eigenschaft einer array expliziten, können Sie eine differenzierte Kontrolle über die Verwendung des gemeinsam genutzten Speichers ausüben, sodass Sie die App auf die Leistungsmerkmale der Hardware basierend auf den Speicherzugriffsmustern ihrer Berechnungskerne optimieren können. Ein array_view Entspricht dem, cpu_access_typearray dem es zugeordnet ist; oder, wenn die array_view ohne Datenquelle erstellt wird, spiegelt sie access_type die Umgebung wider, mit der sie zuerst Speicher zuweist. Das heißt, wenn sie zuerst vom Host (CPU) zugegriffen wird, verhält es sich so, als ob sie über eine CPU-Datenquelle erstellt wurde und die access_type von der accelerator_view Erfassung zugeordneten freigegeben wird. Wenn sie jedoch zuerst von einem accelerator_view, dann verhält es sich so, als ob sie über eine array Erstellte erstellt accelerator_view wurde und die array"s access_type" teilt.

Das folgende Codebeispiel zeigt, wie Sie bestimmen, ob die Standardzugriffstaste freigegebenen Arbeitsspeicher unterstützt, und erstellt anschließend mehrere Arrays mit verschiedenen cpu_access_type-Konfigurationen.

#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);
}

Ausführen von Code zu Daten: parallel_for_each

Die parallel_for_each-Funktion definiert den Code, den Sie für die Zugriffstaste für die Daten im array Oder array_view Objekt ausführen möchten. 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 akzeptiert zwei Argumente, eine Compute-Domäne und einen Lambda-Ausdruck.

Die Compute-Do Standard ist ein extent Objekt oder ein tiled_extent Objekt, das den Satz von Threads definiert, die für die parallele Ausführung erstellt werden sollen. Für jedes Element in der Compute-Domä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 Text des Lambda-Ausdrucks auf alle erfassten Variablen nach Wert zugreift, die in diesem Fall sind a, bund sum. In diesem Beispiel wird in der Parameterliste eine eindimensionale index-Variable mit dem Namen idx erstellt. Der Wert von idx[0] beträgt im ersten Thread null und wird in jedem nachfolgenden Thread um eins erhöht. Die Anweisung restrict(amp) gibt an, dass nur die Teilmenge der Programmiersprache C++ verwendet wird, die mit C++ AMP beschleunigt werden kann. 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 Lambdaausdruck 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, jedoch wird eine separate Kernelfunktion aufgerufen.

#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 eine zusätzliche Beschleunigung erreichen, indem Sie Kacheln verwenden. Bei der Tilung werden die Threads in gleich rechteckige Teilmengen oder Kacheln unterteilt. Sie bestimmen die geeignete Kachelgröße auf der Basis des Datasets und des Algorithmus, den Sie programmieren. Für jeden Thread haben Sie Zugriff auf den globalen Speicherort eines Datenelements relativ zum Gesamten array oder array_view zugriff auf den lokalen Speicherort relativ zur Kachel. Die Verwendung des lokalen Indexwerts vereinfacht den Code, da Sie keinen Code schreiben müssen, um globale Indexwerte in lokale zu übersetzen. Rufen Sie zum Verwenden der Tilung die "extent::tile"-Methode für die Compute-Do Standard 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 verknüpft, und der Code muss in der gesamten Kachel auf Werte zugreifen und diese verfolgen. Verwenden Sie die tile_static Schlüsselwort-Schlüsselwort (keyword) und die tile_barrier::wait-Methode, um dies zu erreichen. Eine Variable mit dem tile_static Schlüsselwort (keyword) verfügt über einen Bereich über eine gesamte Kachel und eine Instanz der Variablen wird für jede Kachel erstellt. Sie müssen sich um die Synchronisierung des Kachel-Threadzugriffs auf die Variable kümmern. 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 beenden, für die der Zugriff auf alle Werte erforderlich ist.

Im folgenden Diagramm wird ein zweidimensionales Array mit Samplingdaten dargestellt, das in Kacheln angeordnet ist.

Index values in a tiled extent.

Im folgenden Codebeispiel werden die Samplingdaten aus dem vorherigen Diagramm verwendet. Im Code wird jeder Wert in der Kachel durch den Durchschnitt der Werte in der Kachel ersetzt.

// 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 Bibliothek mit doppelter Genauigkeit im Concurrency::p recise_math Namespace bietet Unterstützung für Funktionen mit doppelter Genauigkeit. Sie unterstützt auch Funktionen mit einfacher Genauigkeit, obwohl die Unterstützung doppelter Genauigkeit auf der Hardware noch benötigt wird. Sie entspricht der C99-Spezifikation (ISO/IEC 9899).It conforms to the C99 Specification (ISO/IEC 9899). Der Beschleuniger muss doppelte Genauigkeit vollständig unterstützen. Sie können bestimmen, 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 doppelten Mathematischen Bibliothek. 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 die Namespaces als auch in die fast_mathprecise_math Namespaces importiert. Die restrict Schlüsselwort (keyword) wird verwendet, um die <cmath-Version> und die C++-AMP-Version zu unterscheiden. Der folgende Code berechnet mithilfe der schnellen Methode den Logarithmus zur Basis 10 jedes Werts, der in der Berechnungsdomäne enthalten ist.

#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 Programmierung beschleunigter Grafikfunktionen 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 Grafikbibliothek enthält die folgenden Hauptkomponenten:

  • 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 in Bezug auf Die Zuordnung und Kopiererstellung. Weitere Informationen finden Sie unter C++-Standardbibliothekcontainer. Die Vorlagenparameter für die texture-Klasse sind der Elementtyp und der Rang. Der Rang kann 1, 2 oder 3 lauten. Der Elementtyp kann einer der Typen kurzer Vektoren sein, die später 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 , , uint, , float, doublenorm oder unorm basieren.int

Universelle Windows-Plattform-Apps (UWP)

Wie andere C++-Bibliotheken können Sie C++ AMP in Ihren UWP-Apps verwenden. In diesen Artikeln wird beschrieben, wie C++ AMP-Code in Apps verwendet wird, die mit C++, C#, Visual Basic oder JavaScript erstellt werden:

C++ AMP und Parallelitätsschnellansicht

Die Nebenläufigkeitsschnellansicht unterstützt u. a. das Analysieren der Leistung des C++ AMP-Codes. Diese Artikel beschreiben diese Funktionen:

Leistungsempfehlungen in Azure Database for PostgreSQL

Modulo und Division ganzer Zahlen ohne Vorzeichen weisen eine erheblich bessere Leistung als Modulo und Division ganzer Zahlen mit Vorzeichen auf. Es wird empfohlen, ganze Zahlen ohne Vorzeichen zu verwenden, sofern möglich.

Siehe auch

C++ AMP (C++ Accelerated Massive Parallelism)
Lambdaausdruckssyntax
Referenz (C++ AMP)
Parallele Programmierung im Blog "Native Code"