Przegląd C++ AMP

Uwaga

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 żadnych 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

  • Windows Server 2008 R2 do Visual Studio 2019.

  • 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ługiwana w usłudze ARM64.

Wprowadzenie

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} element i {6, 7, 8, 9, 10} uzyskać polecenie {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 języka C++ służą do konstruowania trzech obiektów array_view języka C++. 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 może przyspieszyć C++ AMP.

  • 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 array obiekcie lub array_view przez hermetyzowanie przesunięcia ze źródła w każdym wymiarze do jednego obiektu. Gdy uzyskujesz dostęp do lokalizacji w tablicy, przekazujesz index obiekt do operatora indeksowania , []zamiast listy indeksatoró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

extent — Klasa

Zakres Klasa określa długość danych w każdym wymiarze array obiektu lub array_view . Można utworzyć zakres i użyć go do utworzenia array obiektu lub array_view . Można również pobrać zakres istniejącego array obiektu 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 array_view obiekt, który ma te same wymiary co obiekt w poprzednim przykładzie, ale w tym przykładzie użyto extent obiektu 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 array_view

W bibliotece środowiska uruchomieniowego zdefiniowano dwa kontenery danych używane do przenoszenia danych do akceleratora. Są to tablica Class (Klasa) i array_view Class (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.

array — Klasa

Podczas array konstruowania obiektu tworzona jest głęboka kopia danych w akceleratorze, jeśli używasz konstruktora zawierającego wskaźnik do zestawu danych. Funkcja jądra modyfikuje kopię akceleratora. 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 vector conversion operator funkcji jądra element 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, ponieważ są z konstruktorem 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 array klasami i array_view .

opis array — Klasa array_view — klasa
Po określeniu rangi W czasie kompilacji. W czasie kompilacji.
Po określeniu zakresu W czasie wykonywania. W czasie wykonywania.
Kształt Prostokątne. Prostokątne.
Magazyn danych Jest kontenerem danych. To otoka danych.
Treść reklamy Jawne i głębokie kopiowanie w definicji. Niejawna kopia, gdy jest ona uzyskiwana przez funkcję jądra.
Pobieranie danych Kopiując dane tablicy z powrotem do obiektu w wątku procesora CPU. Bezpośredni dostęp do array_view obiektu lub wywołanie metody array_view::sync w celu kontynuowania uzyskiwania dostępu do danych w oryginalnym kontenerze.

Pamięć udostępniona z tablicą i 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ęć współdzielona jest obsługiwana. Jeśli pamięć współdzielona jest obsługiwana, właściwość określa domyślną access_type Wyliczenie alokacji pamięci w akceleratorze default_cpu_access_type . Domyślnie array obiekty i array_view przyjmują te same obiekty access_type co skojarzone z elementem podstawowym accelerator.

Ustawiając właściwość array array::cpu_access_type Data Member jawnie, możesz wykonywać precyzyjną kontrolę nad sposobem użycia pamięci udostępnionej, aby można było zoptymalizować aplikację pod kątem cech wydajności sprzętu na podstawie wzorców dostępu do pamięci jej jąder obliczeniowych. Element array_view odzwierciedla to samo cpu_access_typearray , co skojarzone z nim, lub, jeśli array_view jest skonstruowany bez źródła danych, odzwierciedla access_type środowisko, które najpierw powoduje przydzielenie magazynu. Oznacza to, że jeśli jest on najpierw uzyskiwany przez hosta (procesor CPU), zachowuje się tak, jakby został utworzony za pośrednictwem źródła danych procesora CPU i współudzieli access_typeaccelerator_view wartość skojarzona przez przechwytywanie. Jeśli jednak jest on najpierw uzyskiwany przez accelerator_viewobiekt , zachowuje się tak, jakby został utworzony za pośrednictwem utworzonego array obiektu accelerator_view i udostępnia arrayaccess_typeelement .

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ć w akceleratorze array względem danych w obiekcie orarray_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. Wskazuje restrict(amp) , że używany jest tylko podzbiór języka C++, który może przyspieszyć 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: kafelki i bariery

Możesz uzyskać dodatkowe przyspieszenie przy użyciu tilingu. 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 dostępu do lokalizacji lokalnej 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ć tilingu, wywołaj metodę extent::tile w domenie obliczeniowej parallel_for_each w metodzie 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 do wątku kafelka do zmiennej. Metoda tile_barrier::wait zatrzymuje wykonywanie bieżącego wątku, dopóki wszystkie wątki na kafelku nie osiągną wywołania metody 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.

Index values in a tiled extent.

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 o podwójnej precyzji w przestrzeni nazw Concurrency::p recise_math zapewnia obsługę funkcji podwójnej precyzji. Zapewnia również obsługę funkcji o pojedynczej precyzji, chociaż obsługa podwójnej precyzji sprzętu jest nadal wymagana. Jest ona zgodna ze specyfikacją C99 (ISO/IEC 9899). Akcelerator musi obsługiwać pełną podwójną precyzję. Możesz określić, czy to robi, sprawdzając wartość elementu 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łówka <amp_math.h> , a wszystkie są deklarowane za pomocą restrict(amp)polecenia . Funkcje w pliku nagłówka <cmath> są importowane do przestrzeni fast_math nazw 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.

  • writeonly_texture_view Klasa: zapewnia 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 platforma uniwersalna 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

Modulo i podział niepodpisanych liczb całkowitych mają znacznie lepszą wydajność niż modulo i podział ze znakiem liczb całkowitych. Zalecamy używanie niepodpisanych liczb całkowitych, jeśli jest to możliwe.

Zobacz też

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