Udostępnij za pośrednictwem


Przegląd C++ AMP

C++ Accelerated Massive Parallelism (C++ AMP) przyspiesza wykonywanie kodu C++, wykorzystując sprzęt obsługujący dane równoległe, takie jak jednostka przetwarzania grafiki (GPU) na dyskretną kartę graficzną.Używając C++ AMP, można kodować algorytmy wielowymiarowych danych, dzięki czemu można przyspieszyć wykonywanie za pomocą równoległości na heterogenicznym sprzęcie.Model programowania C++ AMP zawiera tablice wielowymiarowe, indeksowanie, transfer pamięci, układanie i bibliotekę funkcji matematycznych.Można użyć rozszerzeń języka C++ AMP do kontroli sposobu przenoszenia danych z procesora CPU do GPU i z powrotem, dzięki czemu można zwiększyć wydajność.

Wymagania systemowe

  • Windows 7, Windows 8, Windows Server 2008 z dodatkiem R2 lub Windows Server 2012

  • DirectX 11 poziom funkcji 11.0 lub nowszy sprzęt

  • Do debugowania na emulatorze oprogramowania wymagane są Windows 8 lub Windows Server 2012.Do debugowania na sprzęcie należy zainstalować sterowniki dla karty graficznej.Aby uzyskać więcej informacji, zobacz Debugowanie kodu GPU.

Wprowadzenie

Dwa poniższe przykłady ilustrują podstawowe składniki C++ AMP.Zakładając, że chcesz dodać odpowiadające elementy dwóch tablic jednowymiarowych.Na przykład, można dodać {1, 2, 3, 4, 5} i {6, 7, 8, 9, 10} aby uzyskać {7, 9, 11, 13, 15}.Bez korzystania z C++ AMP można napisać następujący kod, służący do dodawania liczb i wyświetlania wyników.

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

Następujące części kodu są istotne:

  • Dane: Dane składają się z trzech tablic.Wszystkie mają te same rangi (jeden) i długości (pięć).

  • Iteracja: Pierwsza pętla for udostępnia mechanizm do iteracji na elementach w tablicach.Kod, który chcesz wykonać do obliczenia sum, jest zawarty w pierwszym bloku for.

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

Używając C++ AMP, można napisać poniższy kod zamiast kodu podanego w poprzednim przykładzie.

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

Obecne są te same elementy podstawowe, ale użyte są konstrukcje C++ AMP:

  • Dane: Używasz tablic C++ do konstruowania trzech obiektów AMP C++ array_view.Należy dostarczyć czterech wartości do konstruowania obiektu array_view: wartości danych, ranking, typ elementu i długość obiektu array_view, 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 C++, która jest przekazywana do konstruktora, jest jednowymiarowa.Ranga i długość są używane do konstruowania prostokątnego kształtu danych w obiekcie array_view, a wartości danych są używane do wypełnienia tablicy.Biblioteka wykonawcza obejmuje również array — Klasa, która posiada interfejs podobny do klasy array_view i jest omówiona w dalszej części tego artykułu.

  • Iteracja: parallel_for_each — Funkcja (C++ AMP) dostarcza mechanizm do iteracji na elementach danych lub domenę obliczeniową.W tym przykładzie domena obliczeniowa jest określona przez sum.extent.Kod, który chcesz wykonać, zawarty jest w wyrażeniu lambda lub w funkcji jądra.restrict(amp) wskazuje, że używany jest tylko podzbiór języka C++, który C++ AMP może przyspieszyć.

  • Indeks: Zmienna index — Klasa, idx, jest zadeklarowana z rangą równą jeden, aby odpowiadać randze obiektu array_view.Używając indeksu, można uzyskać dostęp do poszczególnych elementów obiektów array_view.

Formowanie i indeksowanie danych: indeks i zakres

Należy zdefiniować wartości danych i zadeklarować kształt danych przed uruchomieniem kodu jądra.Wszystkie dane są zdefiniowane jako tablica (prostokątna) i można zdefiniować tablicę o każdej randze (liczba wymiarów).Dane mogą być dowolnego rozmiaru, w dowolnym z wymiarów.

klasa indeksu

index — Klasa określa lokalizację w obiekcie array lub array_view poprzez hermetyzację przesunięcia z miejsca pochodzenia w każdym wymiarze, do jednego obiektu.Po uzyskaniu dostępu do lokalizacji w tablicy, należy przekazać obiekt index do operatora indeksowania [], zamiast listy indeksów liczb całkowitych.Do elementów z każdego wymiaru można uzyskać dostęp za pomocą array::operator() Operator lub array_view::operator() Operator.

Poniższy przykład tworzy jednowymiarowy indeks, który określa trzeci element w jednowymiarowym obiekcie array_view.Indeks jest używany do drukowania trzeciego elementu w obiekcie array_view.Wynik 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, gdzie wiersz = 1 i kolumna = 2, w dwuwymiarowym obiekcie array_view.Pierwszy parametr w konstruktorze index jest składnikiem wiersza, a drugi parametr jest składnikiem kolumny.Wynik 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 trójwymiarowy indeks, który określa element, gdzie głębokość = 0, wiersz = 1 i kolumna = 3, w trójwymiarowym obiekcie array_view.Należy zwrócić uwagę na to, że pierwszy parametr jest składnikiem głębokości, drugi parametr jest składnikiem wiersza, a trzeci parametr jest składnikiem kolumny.Wynik 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

zakres klasy

extent — Klasa (C++ AMP) określa długość danych, w każdym wymiarze, dla obiektu array lub array_view.Można utworzyć zakres i użyć go do utworzenia obiektu array lub array_view.Można także pobrać zakres istniejącego obiektu array lub array_view.Poniższy przykład drukuje długość zakresu w każdym wymiarze obiektu array_view.

int aCPP[] = {
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 
    1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// There are 3 rows and 4 columns, and the depth is two.
array_view<int, 3> a(2, 3, 4, aCPP); 
std::cout << "The number of columns is " << a.extent[2] << "\n";
std::cout << "The number of rows is " << a.extent[1] << "\n";
std::cout << "The depth is " << a.extent[0]<< "\n";
std::cout << "Length in most significant dimension is " << a.extent[0] << "\n";

Poniższy przykład tworzy obiekt array_view, który ma takie same wymiary jak obiekt w poprzednim przykładzie, ale w tym przykładzie użyty jest obiekt extent zamiast używania 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: array i array_view

W bibliotece wykonawczej są zdefiniowane dwa kontenery danych, używane do przenoszenia danych do akceleratora.Są to array — Klasa i array_view — Klasa.Klasa array jest klasą kontenera, która tworzy głęboką kopię danych, gdy obiekt jest konstruowany.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 tablicy

Gdy obiekt array jest konstruowany, tworzona jest głęboka kopia danych na akceleratorze, jeżeli został użyty konstruktor, który zawiera wskaźnik do zestawu danych.Funkcja jądra modyfikuje kopię na akceleratorze.Po zakończeniu wykonywania funkcji jądra należy skopiować dane do struktury źródła danych.Poniższy przykład mnoży każdy element wektora przez 10.Po zakończeniu funkcji jądra operator konwersji wektora jest używany do kopiowania danych do obiektu wektora.

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

klasa array_view

array_view ma prawie te same elementy członkowskie co klasa array, ale jej zachowanie podstawowe nie jest takie samo.Dane przekazywane do konstruktora array_view nie są replikowane na GPU, tak jak w konstruktorze array.Zamiast tego dane są kopiowane do akceleratora po wykonaniu funkcji jądra.Dlatego, jeśli utworzysz dwa obiekty array_view, które używają tych samych danych, oba obiekty array_view odnoszą się do tego samego obszaru pamięci.Po wykonaniu tej czynności należy zsynchronizować każdy dostęp do wielu wątków.Główną zaletą używania klasy array_view jest to, że dane są przenoszone tylko wtedy, gdy jest to konieczne.

Porównanie klas array i array_view

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

Opis

klasa tablicy

klasa array_view

Kiedy ustalana jest ranga

W czasie kompilacji.

W czasie kompilacji.

Kiedy określany jest zakres

W czasie wykonywania.

W czasie wykonywania.

Kształt

Prostokątny.

Prostokątny.

Przechowywanie danych

Jest kontenerem danych.

Jest otoką danych.

Kopiuj

Definicja jawnego i głębokiego kopiowania.

Niejawne kopiowanie, gdy dostęp uzyskuje funkcja jądra.

Pobieranie danych

Przez skopiowanie danych z tablicy z powrotem do obiektu w wątku procesora.

Przez bezpośredni dostęp do obiektu array_view lub przez wywołanie metody array_view::synchronize — Metoda do dalszego uzyskiwania dostępu do danych w oryginalnym kontenerze.

Pamięć współużytkowana z tablicą i array_view

Pamięć współużytkowana jest pamięcią, do której ma dostęp zarówno Procesor, jak i akcelerator.Wykorzystanie pamięci współdzielonej eliminuje lub znacznie zmniejsza obciążenie kopiowania danych między procesorem a akceleratorem.Mimo,że pamięć jest udostępniona, nie może uzyskać do niej dostępu ani procesor, ani akcelerator, a czynność ta powoduje niezdefiniowane zachowanie.

Obiekty array umożliwiają określenie dokładnej kontroli wykorzystania pamięci współdzielonej, jeśli skojarzony akcelerator obsługuje tę funkcję.To, czy akcelerator obsługuje pamięć współużytkowaną jest określane przez właściwość supports_cpu_shared_memory akceleratora, która zwraca true jeśli pamięci współużytkowana jest obsługiwana.Jeśli współużytkowana pamięć jest obsługiwana, domyślny atrybut access_type — Wyliczenie dla alokacji pamięci w akceleratorze jest określana przez właściwość default_cpu_access_type .Domyślnie obiekty array i array_view działają tak samo access_type jako podstawowe związane accelerator.

Przez ustawienie właściwości array::cpu_access_type — Członek danych na jawnie array użytkownik może kontrolować szczegółowe zasady wykonywania kontroli nad współużytkowaną pamięcią, aby zoptymalizować aplikację dla cech wydajności sprzętu, na podstawie wzorców dostępu do pamięci w jądrach obliczeniowych.array_view odzwierciedla tę samą cpu_access_type jako array, z którą jest skojarzona; lub, jeśli array_view jest skonstruowany bez źródła danych, jego access_type odzwierciedla środowisko, w którym po raz pierwszy powoduje przydzielanie pamięci masowej.To znaczy, jeśli po raz pierwszy uzyskuje się dostęp przez hosta (CPU), następnie działa tak, jakby zostały utworzone przez źródło danych CPU i udostępnia access_type z accelerator_view skojarzony przez przechwytywanie; jednak, jeśli po raz pierwszy uzyskuje się dostęp przez accelerator_view, a następnie działa tak, jakby zostały utworzone przez array tworzone na tym accelerator_view i udostępnia arrayw access_type.

Poniższy przykład kodu pokazuje, jak ustalić, czy akcelerator domyślny obsługuje pamięć współużytkowaną, 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 na danych: parallel_for_each

Funkcja parallel_for_each definiuje kod, który należy uruchomić na akceleratorze przeciwko danym w obiekcie array lub array_view.Należy rozważyć następujący kod, znajdujący się w sekcji wprowadzenia, w tym temacie.

#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 obiektem tiled_extent, który definiuje zestaw wątków do utworzenia dla przetwarzania równoległego.Jeden wątek jest generowany dla każdego elementu w domenie obliczeniowej.W tym przypadku obiekt extent jest jednowymiarowy i zawiera pięć elementów.Dlatego też uruchamianych jest pięć wątków.

Wyrażenie lambda definiuje kod, do uruchomienia w każdym wątku.Klauzula przechwytywania [=] określa, że ciało wyrażenia lambda ma dostęp do wszystkich przechwyconych zmiennych według wartości, które w tym przypadku to a, b i sum.W tym przykładzie lista parametrów tworzy jednowymiarową zmienną index o nazwie idx.Wartość idx[0] w pierwszym wątku wynosi 0 i zwiększa się o jeden w każdym kolejnym wątku.restrict(amp) wskazuje, że używany jest tylko podzbiór języka C++, który C++ AMP może przyspieszyć. Ograniczenia funkcji, które mają modyfikator ograniczeń, opisane są w Klauzula ograniczenia (C++ AMP).Aby uzyskać więcej informacji, zobacz Składnia wyrażenia lambda.

Wyrażenie lambda może zawierać kod do wykonania lub może wywołać oddzielną funkcję jądra.Funkcja jądra musi zawierać modyfikator restrict(amp).Poniższy przykład jest równoważny z poprzednim, 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";
    }
}

Kod przyspieszenia: Fragmenty i bariery

Dodatkowe przyspieszenie można uzyskać za pomocą fragmentowania.Fragmentowanie dzieli wątki na równe, prostokątne podzestawy lub fragmenty.Można określić odpowiedni rozmiar fragmentu, na podstawie zestawu danych i algorytmu, który jest kodowany.Dla każdego wątku można mieć dostęp do lokalizacji globalnej elementu danych, w stosunku do całości array lub array_view i dostęp do lokalizacji lokalnej względem fragmentu.Używanie lokalnej wartości indeksu upraszcza kod, ponieważ nie trzeba pisać kodu do przekształcania wartości indeksu, z globalnej na lokalną.W celu użycia fragmentowania należy wywołać extent::tile — Metoda w domenie obliczeniowej, w metodzie parallel_for_each i wykorzystać obiekt tiled_index w wyrażeniu lambda.

W typowych aplikacjach elementy we fragmencie są powiązane w jakiś sposób, a kod musi mieć dostęp i śledzić wartości w całym fragmencie.Użyj słowa kluczowego tile_static słowa kluczowego oraz tile_barrier::wait — Metoda, aby to osiągnąć.Zmienna, której słowo kluczowe tile_static ma zakres na całym fragmencie, a wystąpienie zmiennej jest tworzone dla każdego fragmentu.Należy obsłużyć synchronizację dostępu wątków fragmentu do zmiennej.tile_barrier::wait — Metoda zatrzymuje wykonywanie bieżącego wątku, aż wszystkie wątki we fragmencie osiągną wywołanie tile_barrier::wait.A więc można zbierać wartości z całego fragmentu, za pomocą zmiennych tile_static.Następnie można zakończyć wszelkie obliczenia, które wymagają dostępu do wszystkich wartości.

Poniższy diagram przedstawia dwuwymiarową tablicę danych próbkowania, które są ułożone we fragmenty.

Indeks wartości sąsiadująco zakresu

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

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

C++ AMP zawiera dwie biblioteki funkcji matematycznych.Biblioteka podwójnej precyzji w Concurrency::precise_math — Przestrzeń nazw zapewnia obsługę funkcji podwójnej precyzji.Dostarcza również wsparcie dla funkcji pojedynczej precyzji, chociaż nadal wymagane jest wsparcie sprzętu, dla podwójnej precyzji.Jest on zgodny ze specyfikacją C99 (ISO/IEC 9899).Akcelerator musi w pełni obsługiwać podwójną precyzję.Można określić, czy jest ona spełniona, przez sprawdzenie wartości dla accelerator::supports_double_precision — Członek danych.Szybka biblioteka funkcji matematycznych, w Concurrency::fast_math — Przestrzeń nazw, zawiera inny zestaw funkcji matematycznych.Te funkcje, które obsługują tylko operatory typu float, wykonują się dużo szybciej, ale nie są tak dokładne jak te w bibliotece funkcji matematycznych podwójnej precyzji.Funkcje są zawarte w pliku nagłówka <amp_math.h> i wszystkie są zadeklarowane jako restrict(amp).Funkcje w pliku nagłówka <cmath> są importowane do obu przestrzeni nazw fast_math i precise_math.Słowo kluczowe restrict jest używane do odróżniania wersji <cmath> i wersji C++ AMP.Poniższy kod oblicza logarytm dziesiętny, przy użyciu metody szybkiej, dla 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(logs[idx]);
        }
    );

    for (int i = 0; i < 6; i++) {
        std::cout << logs[i] << "\n";
    }
}

Biblioteka graficzna

C++ AMP zawiera bibliotekę graficzną, która jest przeznaczona do przyspieszonego programowania grafiki.Ta biblioteka jest używana tylko na urządzeniach, które obsługują macierzystą funkcjonalność graficzną.Metody znajdują się w Concurrency::graphics — Przestrzeń nazw i są zawarte w pliku nagłówka <amp_graphics.h>.Kluczowe składniki biblioteki graficznej to:

  • texture — Klasa: Można użyć klasy texture do utworzenia tekstur z pamięci lub pliku.Tekstury przypominają tablice, ponieważ zawierają one dane i przypominają kontenery w Standard Template Library (STL) w odniesieniu do konstrukcji przydziału i kopiowania.Aby uzyskać więcej informacji, zobacz Kontenery STL.Parametry szablonu dla klasy texture są typami elementu i rangi.Ranga może wynosić 1, 2 lub 3.Typ elementu może być jednym z typów krótkich wektorów, które są opisane w dalszej części tego artykułu.

  • writeonly_texture_view — Klasa: zapewnia dostęp tylko do zapisu, do wszelkich tekstur.

  • Krótkie biblioteki wektorowe: Definiuje zestaw typów krótkich wektorów, o długości 2, 3 i 4, opartych na int, uint, float, double, norm lub unorm.

Windows Store Aplikacje

Podobnie jak inne biblioteki C++, można użyć C++ AMP w aplikacjach Windows Store.Artykuły te opisują, jak dołączyć kod C++ AMP w aplikacjach utworzonych za pomocą języka C++, C#, Visual Basic lub języka JavaScript:

C++ AMP i Concurrency Visualizer

Concurrency Visualizer zawiera obsługę dla analizowania wydajności kodu C++ AMP.Artykuły te opisują następujące funkcje:

Zalecenia dotyczące wydajności

Wyznaczanie modułu i dzielenie liczb całkowitych bez znaku ma znacznie lepszą wydajność niż wyznaczanie modułu i dzielenie liczb całkowitych ze znakiem.Zalecane jest, aby korzystać z liczb całkowitych bez znaku, gdy jest to możliwe.

Zobacz też

Informacje

Składnia wyrażenia lambda

Inne zasoby

C++ AMP (C++ Accelerated Massive Parallelism)

Odwołanie (C++ AMP)

Programowanie równoległe w kodzie macierzystym bloga