Udostępnij za pośrednictwem


Omówienie C++ AMP

Uwaga / Notatka

Nagłówki C++ AMP są przestarzałe, począwszy od programu Visual Studio 2022 w wersji 17.0. Dołączenie wszystkich nagłówków AMP spowoduje wygenerowanie błędów kompilacji. Zdefiniuj _SILENCE_AMP_DEPRECATION_WARNINGS przed dołączeniem jakichkolwiek nagłówków AMP, aby wyciszyć ostrzeżenia.

Przyspieszona równoległość języka C++ (C++ AMP) przyspiesza wykonywanie kodu C++, korzystając z sprzętu równoległego danych, takiego jak jednostka przetwarzania grafiki (GPU) na dyskretnej karcie graficznej. Używając języka C++ AMP, można kodować wielowymiarowe algorytmy danych, aby można było przyspieszyć wykonywanie przy użyciu równoległości na sprzęcie heterogenicznym. Model programowania C++ AMP zawiera wielowymiarowe tablice, indeksowanie, transfer pamięci, tiling i bibliotekę funkcji matematycznych. Za pomocą rozszerzeń języka C++ AMP można kontrolować sposób przenoszenia danych z procesora CPU do procesora GPU i z powrotem, aby zwiększyć wydajność.

Wymagania systemowe

  • Windows 7 lub nowszy

  • Od Windows Server 2008 R2 do Visual Studio 2019 włącznie.

  • Sprzęt funkcji DirectX 11 na poziomie 11.0 lub nowszym

  • Do debugowania w emulatorze oprogramowania wymagany jest system Windows 8 lub Windows Server 2012. Aby debugować na sprzęcie, należy zainstalować sterowniki dla karty graficznej. Aby uzyskać więcej informacji, zobacz Debugowanie kodu procesora GPU.

  • Uwaga: AMP nie jest obecnie obsługiwany na ARM64.

Introduction

W poniższych dwóch przykładach przedstawiono podstawowe składniki języka C++ AMP. Załóżmy, że chcesz dodać odpowiednie elementy dwóch jednowymiarowych tablic. Na przykład możesz dodać {1, 2, 3, 4, 5} i {6, 7, 8, 9, 10} aby uzyskać {7, 9, 11, 13, 15}. Bez używania języka C++ AMP możesz napisać następujący kod, aby dodać liczby i wyświetlić wyniki.

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

Ważne części kodu są następujące:

  • Dane: dane składają się z trzech tablic. Wszystkie mają tę samą rangę (jedną) i długość (pięć).

  • Iteracja: pierwsza for pętla zapewnia mechanizm iteracji przez elementy w tablicach. Kod, który chcesz wykonać w celu obliczenia sum, znajduje się w pierwszym for bloku.

  • Indeks: zmienna idx uzyskuje dostęp do poszczególnych elementów tablic.

Używając języka C++ AMP, możesz zamiast tego napisać następujący kod.

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

Istnieją te same podstawowe elementy, ale używane są konstrukcje AMP języka C++:

  • Dane: tablice C++ są używane do tworzenia trzech obiektów C++ AMP array_view. Należy podać cztery wartości, aby utworzyć array_view obiekt: wartości danych, rangę, typ elementu i długość array_view obiektu w każdym wymiarze. Ranga i typ są przekazywane jako parametry typu. Dane i długość są przekazywane jako parametry konstruktora. W tym przykładzie tablica języka C++, która jest przekazywana do konstruktora, jest jednowymiarowa. Ranga i długość służą do konstruowania prostokątnego kształtu danych w array_view obiekcie, a wartości danych są używane do wypełniania tablicy. Biblioteka środowiska uruchomieniowego zawiera również klasę tablicy, która zawiera interfejs podobny do array_view klasy i jest omówiony w dalszej części tego artykułu.

  • Iteracja: funkcja parallel_for_each (C++ AMP) udostępnia mechanizm iteracji za pośrednictwem elementów danych lub domeny obliczeniowej. W tym przykładzie domena obliczeniowa jest określana przez sum.extent. Kod, który chcesz wykonać, jest zawarty w wyrażeniu lambda lub funkcji jądra. Wskazuje restrict(amp), że używany jest tylko podzbiór języka C++, który C++ AMP może przyspieszyć.

  • Indeks: zmienna klasy indeksu , idxjest zadeklarowana z jedną rangą, aby dopasować rangę array_view obiektu. Za pomocą indeksu można uzyskać dostęp do poszczególnych elementów array_view obiektów.

Kształtowanie i indeksowanie danych: indeksowanie i zakres

Przed uruchomieniem kodu jądra należy zdefiniować wartości danych i zadeklarować kształt danych. Wszystkie dane są definiowane jako tablica (prostokątna) i można zdefiniować tablicę tak, aby miała dowolną rangę (liczbę wymiarów). Dane mogą mieć dowolny rozmiar w dowolnym wymiarze.

index — Klasa

Klasa indeksu określa lokalizację w obiekcie array lub array_view poprzez enkapsulowanie przesunięcia od początku w każdym wymiarze do jednego obiektu. Gdy uzyskujesz dostęp do lokalizacji w tablicy, przekazujesz index obiekt do operatora indeksowania, [], zamiast listy indeksów całkowitych. Dostęp do elementów w każdym wymiarze można uzyskać przy użyciu operatora array::operator() lub operatora array_view::operator().

Poniższy przykład tworzy indeks jednowymiarowy, który określa trzeci element w obiekcie jednowymiarowym array_view . Indeks jest używany do drukowania trzeciego elementu w array_view obiekcie. Dane wyjściowe to 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

Poniższy przykład tworzy dwuwymiarowy indeks, który określa element, w którym wiersz = 1 i kolumna = 2 w obiekcie dwuwymiarowym array_view . Pierwszy parametr w konstruktorze jest składnikiem index wiersza, a drugi parametr jest składnikiem kolumny. Dane wyjściowe to 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

Poniższy przykład tworzy indeks trójwymiarowy, który określa element, w którym głębokość = 0, wiersz = 1, a kolumna = 3 w obiekcie trójwymiarowym array_view . Zwróć uwagę, że pierwszy parametr jest składnikiem głębokości, drugim parametrem jest składnik wiersza, a trzeci parametr jest składnikiem kolumny. Dane wyjściowe to 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

klasa extent

Klasa Extent określa długość danych w każdym wymiarze obiektu array lub array_view. Można utworzyć zakres i użyć go do utworzenia array lub array_view obiektu. Można również pobrać rozmiar istniejącego obiektu array lub array_view. Poniższy przykład wyświetla długość zakresu w każdym wymiarze array_view obiektu.

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

Poniższy przykład tworzy obiekt array_view, który ma te same wymiary jak obiekt w wcześniejszym przykładzie, ale tutaj użyto obiektu extent zamiast jawnych parametrów w konstruktorze 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";

Przenoszenie danych do akceleratora: tablica i widok tablicy

W bibliotece środowiska uruchomieniowego zdefiniowano dwa kontenery danych używane do przenoszenia danych do akceleratora. Są to Klasa Array i Klasa Array_view. Klasa array jest klasą kontenera, która tworzy głęboką kopię danych podczas konstruowania obiektu. Klasa array_view jest klasą otoki, która kopiuje dane, gdy funkcja jądra uzyskuje dostęp do danych. Gdy dane są potrzebne na urządzeniu źródłowym, dane są kopiowane z powrotem.

Klasa array

Podczas konstruowania obiektu array, jeśli używasz konstruktora z wskaźnikiem do zestawu danych, na akceleratorze tworzona jest głęboka kopia danych. Funkcja jądra modyfikuje kopię znajdującą się na akceleratorze. Po zakończeniu wykonywania funkcji jądra należy skopiować dane z powrotem do struktury danych źródłowych. Poniższy przykład mnoży każdy element w wektorze przez 10. Po zakończeniu funkcji jądra, vector conversion operator jest używany do kopiowania danych z powrotem do obiektu wektorowego.

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 — Klasa

Element array_view ma prawie te same elementy członkowskie co array klasa, ale podstawowe zachowanie nie jest takie samo. Dane przekazywane do konstruktora array_view nie są replikowane na procesorze GPU, w przeciwieństwie do konstruktora array. Zamiast tego dane są kopiowane do akceleratora po wykonaniu funkcji jądra. W związku z tym w przypadku utworzenia dwóch array_view obiektów, które używają tych samych danych, oba array_view obiekty odwołują się do tego samego miejsca w pamięci. W takim przypadku należy zsynchronizować dowolny dostęp wielowątkowy. Główną zaletą korzystania z array_view klasy jest to, że dane są przenoszone tylko wtedy, gdy jest to konieczne.

Porównanie tablic i array_view

W poniższej tabeli podsumowano podobieństwa i różnice między klasami array i array_view.

Opis klasa Array Klasa array_view
Kiedy określana jest ranga W czasie kompilacji. W czasie kompilacji.
Po określeniu zakresu W czasie wykonywania. W czasie działania.
Kształt Prostokątne. Prostokątne.
Magazyn danych Jest kontenerem danych. To opakowanie danych.
Kopiować Eksplityczne i głębokie kopiowanie przy definiowaniu. Niejawna kopia, gdy jest ona uzyskiwana przez funkcję jądra.
Pobieranie danych Kopiując dane tablicy z powrotem do obiektu na wątku procesora. Bezpośredni dostęp do array_view obiektu lub wywołanie metody array_view::synchronize, aby kontynuować dostęp do danych w oryginalnym kontenerze.

Pamięć udostępniona z tablicą i widokiem tablicy (array_view)

Pamięć współdzielona to pamięć, do którego można uzyskać dostęp zarówno przez procesor CPU, jak i akcelerator. Użycie pamięci udostępnionej eliminuje lub znacznie zmniejsza nakład pracy związany z kopiowaniem danych między procesorem CPU a akceleratorem. Mimo że pamięć jest współdzielona, nie można uzyskać do niej dostępu współbieżnie zarówno przez procesor, jak i akcelerator, i powoduje to niezdefiniowane zachowanie.

array obiekty mogą służyć do określania szczegółowej kontroli nad użyciem pamięci udostępnionej, jeśli skojarzony akcelerator go obsługuje. To, czy akcelerator obsługuje pamięć współdzieloną, zależy od właściwości supports_cpu_shared_memory akceleratora, która zwraca true wartość, gdy pamięć udostępniona jest obsługiwana. Jeśli pamięć współdzielona jest obsługiwana, domyślne Wyliczenie typu dostępu dla alokacji pamięci na akceleratorze jest określane przez właściwość default_cpu_access_type. Domyślnie obiekty array i array_view przyjmują tę samą access_type co główny skojarzony accelerator.

Ustawiając właściwość array::cpu_access_type Data Member jawnie, możesz precyzyjnie kontrolować sposób użycia pamięci udostępnionej, dzięki czemu możesz zoptymalizować aplikację pod względem wydajności sprzętu na podstawie wzorców dostępu do pamięci jąder obliczeniowych. Element array_view odwzorowuje to samo cpu_access_type, co element array, z którym jest skojarzony, lub jeśli array_view jest skonstruowany bez źródła danych, jego access_type odzwierciedla środowisko, które najpierw powoduje alokację pamięci. Oznacza to, że jeśli najpierw dostęp do niego uzyskuje host (procesor CPU), zachowuje się tak, jakby został utworzony na bazie źródła danych CPU i współdzieli access_typeaccelerator_view skojarzone przez przechwytywanie. Jednak gdy najpierw dostęp uzyskuje accelerator_view, zachowuje się tak, jakby został utworzony na array utworzonym na tym accelerator_view i dzieli arrayaccess_type.

Poniższy przykład kodu pokazuje, jak określić, czy akcelerator domyślny obsługuje pamięć współdzieloną, a następnie tworzy kilka tablic, które mają różne konfiguracje cpu_access_type.

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

Wykonywanie kodu za pośrednictwem danych: parallel_for_each

Funkcja parallel_for_each definiuje kod, który chcesz uruchomić na akceleratorze względem danych w obiekcie array lub array_view. Rozważ poniższy kod z wprowadzenia tego tematu.

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

Metoda parallel_for_each przyjmuje dwa argumenty: domenę obliczeniową i wyrażenie lambda.

Domena obliczeniowa jest obiektem extent lub obiektemtiled_extent, który definiuje zestaw wątków do utworzenia na potrzeby wykonywania równoległego. Jeden wątek jest generowany dla każdego elementu w domenie obliczeniowej. W tym przypadku extent obiekt jest jednowymiarowy i ma pięć elementów. W związku z tym są uruchamiane pięć wątków.

Wyrażenie lambda definiuje kod do uruchomienia w każdym wątku. Klauzula capture określa, [=]że treść wyrażenia lambda uzyskuje dostęp do wszystkich przechwyconych zmiennych według wartości, co w tym przypadku to a, bi sum. W tym przykładzie lista parametrów tworzy zmienną jednowymiarową index o nazwie idx. Wartość parametru idx[0] wynosi 0 w pierwszym wątku i zwiększa się o jedną w każdym kolejnym wątku. restrict(amp) wskazuje, że używany jest tylko ten podzbiór języka C++, który może być przyspieszany przez C++ AMP. Ograniczenia dotyczące funkcji, które mają modyfikator ograniczeń, zostały opisane w temacie Restrict (C++ AMP). Aby uzyskać więcej informacji, zobacz Składnia wyrażeń lambda.

Wyrażenie lambda może zawierać kod do wykonania lub może wywołać oddzielną funkcję jądra. Funkcja jądra musi zawierać restrict(amp) modyfikator. Poniższy przykład jest odpowiednikiem poprzedniego przykładu, ale wywołuje oddzielną funkcję jądra.

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

Przyspieszanie kodu: płytki i bariery

Możesz uzyskać dodatkowe przyspieszenie, korzystając z kafelkowania. Tiling dzieli wątki na równe prostokątne podzestawy lub kafelki. Określasz odpowiedni rozmiar kafelka na podstawie zestawu danych i algorytmu, który kodujesz. Dla każdego wątku masz dostęp do globalnej lokalizacji elementu danych względem całości array lub array_view, a także dostęp do lokalnej lokalizacji względem kafelka. Użycie wartości indeksu lokalnego upraszcza kod, ponieważ nie trzeba pisać kodu w celu tłumaczenia wartości indeksu z globalnego na lokalny. Aby użyć kafelkowania, wywołaj metodę extent::tile w metodzie parallel_for_each na domenie obliczeniowej i użyj obiektu tiled_index w wyrażeniu lambda.

W typowych aplikacjach elementy kafelka są powiązane w jakiś sposób, a kod musi uzyskiwać dostęp do kafelków i śledzić wartości na kafelku. Użyj słowa kluczowego tile_static i metody tile_barrier::wait , aby to osiągnąć. Zmienna zawierająca słowo kluczowe tile_static ma zakres w całym kafelku, a wystąpienie zmiennej jest tworzone dla każdego kafelka. Należy obsługiwać synchronizację dostępu wątku kafelkowego do zmiennej. Metoda tile_barrier::wait zatrzymuje wykonywanie bieżącego wątku, dopóki wszystkie wątki w kafelku nie osiągną wywołania tile_barrier::wait. Dzięki temu można gromadzić wartości na kafelku przy użyciu zmiennych tile_static . Następnie możesz zakończyć wszystkie obliczenia, które wymagają dostępu do wszystkich wartości.

Na poniższym diagramie przedstawiono dwuwymiarową tablicę danych próbkowania rozmieszczonych na kafelkach.

Wartości indeksu w zakresie kafelków.

Poniższy przykład kodu używa danych próbkowania z poprzedniego diagramu. Kod zastępuje każdą wartość na kafelku przez średnią wartości na kafelku.

// 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

Biblioteki matematyczne

Język C++ AMP zawiera dwie biblioteki matematyczne. Biblioteka podwójnej precyzji w przestrzeni nazw Concurrency::precise_math zapewnia obsługę funkcji podwójnej precyzji. Zapewnia również obsługę funkcji o pojedynczej precyzji, chociaż wymagana jest obsługa podwójnej precyzji na sprzęcie. Jest ona zgodna ze specyfikacją C99 (ISO/IEC 9899). Akcelerator musi obsługiwać pełną podwójną precyzję. Można określić, czy to robi, sprawdzając wartość akceleratora::supports_double_precision elementu członkowskiego danych. Szybka biblioteka matematyczna w przestrzeni nazw Concurrency::fast_math zawiera inny zestaw funkcji matematycznych. Te funkcje, które obsługują tylko float operandy, są wykonywane szybciej, ale nie są tak precyzyjne, jak w bibliotece matematycznej o podwójnej precyzji. Funkcje są zawarte w pliku nagłówkowym <amp_math.h>, a wszystkie są deklarowane za pomocą restrict(amp). Funkcje w pliku nagłówkowym <cmath> są importowane do przestrzeni nazw fast_math i precise_math. Słowo restrict kluczowe służy do rozróżniania <wersji programu cmath> i wersji C++ AMP. Poniższy kod oblicza logarytm base-10 przy użyciu metody szybkiej każdej wartości, która znajduje się w domenie obliczeniowej.

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

Biblioteka grafiki

Język C++ AMP zawiera bibliotekę graficzną przeznaczoną do przyspieszonego programowania grafiki. Ta biblioteka jest używana tylko na urządzeniach, które obsługują natywną funkcjonalność grafiki. Metody znajdują się w przestrzeni nazw Concurrency::graphics i znajdują się w pliku nagłówkowym <amp_graphics.h> . Kluczowe składniki biblioteki grafiki to:

  • texture Class (klasa tekstury): możesz użyć klasy tekstur, aby utworzyć tekstury z pamięci lub pliku. Tekstury przypominają tablice, ponieważ zawierają dane i przypominają kontenery w standardowej bibliotece języka C++ w odniesieniu do tworzenia przypisań i kopiowania. Aby uzyskać więcej informacji, zobacz Standardowe kontenery bibliotek języka C++. Parametry szablonu dla texture klasy to typ elementu i ranga. Ranga może być 1, 2 lub 3. Typ elementu może być jednym z krótkich typów wektorów opisanych w dalszej części tego artykułu.

  • Klasa writeonly_texture_view: umożliwia dostęp tylko do zapisu do dowolnej tekstury.

  • Biblioteka krótkich wektorów: definiuje zestaw krótkich typów wektorów o długości 2, 3 i 4, które są oparte na int, uint, float, double, normie lub unorm.

Aplikacje platformy uniwersalnej systemu Windows (UWP)

Podobnie jak w przypadku innych bibliotek języka C++, można użyć języka C++ AMP w aplikacjach platformy UWP. W tych artykułach opisano sposób dołączania kodu C++ AMP do aplikacji tworzonych przy użyciu języków C++, C#, Visual Basic lub JavaScript:

C++ AMP i Concurrency Visualizer

Wizualizator współbieżności obejmuje obsługę analizowania wydajności kodu C++ AMP. W tych artykułach opisano następujące funkcje:

Zalecenia dotyczące wydajności

Moduł i dzielenie liczb całkowitych bez znaku mają znacznie lepszą wydajność niż moduł i dzielenie liczb całkowitych ze znakiem. Zalecamy używanie niepodpisanych liczb całkowitych, jeśli jest to możliwe.

Zobacz także

C++ AMP (C++ Accelerated Massive Parallelism)
Składnia wyrażeń lambda
Dokumentacja (C++ AMP)
Blog o programowaniu równoległym w kodzie natywnym