共用方式為


C++ AMP 概觀

C++ 加速超級的平行處理原則 (C++ AMP) 加速 C++ 程式碼執行利用資料平行硬體,例如處理在中斷模式的圖形單位 (GPU)。 使用 C++ AMP,您可以撰寫多維資料演算法,利用異質硬體上的平行處理加速執行。 C++ AMP 程式撰寫模型包含多維陣列、索引、記憶體傳輸、並排顯示和數學函式程式庫。 您可以使用 AMP C++ 語言擴充功能控制項資料的方式從 CPU 移至 GPU 和參考,因此,您可以改善效能。

系統需求

  • Windows 7、Windows 8、Windows Server 2008 R2 或 Windows Server 2012

  • DirectX 11 功能層級 11.0 或較新硬體

  • 對於軟體模擬器偵錯時,需要 Windows 8 或 Windows Server 2012 。 如需在硬體的偵錯,您必須安裝圖形卡的驅動程式。 如需詳細資訊,請參閱偵錯 GPU 程式碼

簡介

下列兩個範例說明 C++ AMP 的主要元件。 假設您要新增兩個一維陣列的對應元素。 例如,您可能想要新增 {1, 2, 3, 4, 5} 及 {6, 7, 8, 9, 10} 以取得 {7, 9, 11, 13, 15}。 如果不使用 C++ AMP,您可以撰寫下列程式碼將數字相加並顯示結果。

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

程式碼的重點如下:

  • 資料:資料包含三個陣列。 全部具有相同的陣序 (1) 和長度 (5)。

  • 反覆項目:第一個 for 迴圈提供一個可以反覆經過陣列中的元素機制。 您要執行計算總和的程式碼在第一個 for 區塊中。

  • 索引: idx 變數存取陣列中的個別項目。

使用 C++ AMP,您可以撰寫下列程式碼。

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

相同的基本項目存在,不過,使用 C++ AMP 建構:

  • 資料:您可以使用 C++ 陣列建構有三個 AMP array_view 物件。 您提供四個值來建構 array_view 物件:每個維度的資料值、順位、元素型別和 array_view 物件的長度。 層級和型別會被以型別參數傳遞。 資料和長度將會以建構函式參數傳遞。 在此範例中,傳遞至建構函式的 C++ 陣列是一維陣列。 層級和長度用來建構 array_view 物件中的矩型資料,並將資料值填入陣列。 執行階段程式庫也包含 array 類別,具有介面本文類似 array_view 類別而我們稍後會進行討論。

  • 反覆項目: parallel_for_each 函式 (C++ AMP) 為重複提供一個機制可以透過資料項目或計算網域。 在此範例中,估計網域將由 sum.extent指定。 您要執行的程式碼在 Lambda 運算式或 驗證函式中。 restrict(amp) 表示 C++ AMP 可以加速使用 C++ 語言的子集。

  • 索引: index 類別 變數, idx,宣告層級一個符合 array_view 物件的層級。 您可以使用索引,存取 array_view 物件的個別項目。

模型和索引資料:索引和範圍。

您必須先定義資料值和宣告資料的形式,才能執行核心程式碼。 所有資料都已定義為陣列 (Rectangle),然後,您可以定義陣列有任何的陣序 (維度數目)。 資料可以是任何大小的任何維度。

Hh265136.collapse_all(zh-tw,VS.110).gifindex 類別

index 類別 指定了一個位於 arrayarray_view 物件的位置,方法為封裝每個維度從原點開始的位移至物件中。 當您存取陣列中的位置,傳遞至索引運算子 []的 index 物件,而不是整數索引清單。 您可以使用 array::operator() 運算子array_view::operator() 運算子,存取每個維度的項目。

下列範例會建立一維 array_view 物件指定三個項目的第一維索引。 索引是用來列印 array_view 物件中的第三個項目。 輸出為 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

下列範例會建立二維 array_view 物件中指定項目的= 1 和欄位= 2 的二維索引。 index 建構函式的第一個參數是執行元件,第二個參數是欄位元件。 輸出為 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

下列範例會建立一個立體 array_view 物件中指定項目深度= 0,列 1 =和欄位= 3 的三維索引。 請注意第一個參數是深度元件,第二個參數是資料列元件,而第三個參數是欄位元件。 輸出為 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

Hh265136.collapse_all(zh-tw,VS.110).gifextent 類別

extent 類別 (C++ AMP) 指定了在 arrayarray_view 物件中每個維度資料長度。 您可以建立範圍並使用它來建立 arrayarray_view 物件。 您也可以擷取現有的 arrayarray_view 物件範圍。 下列範例會在 array_view 物件的每個維度 (Dimension) 的列印範圍的長度。

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

下列範例會建立具維度與前一範例中的物件相同的 array_view 物件,不過,這個範例會使用 extent 物件而不是使用 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";

移動資料至加速器:陣列和 array_view

兩個用來將資料移到加速器的資料容器都定義在執行階段程式庫中。 它們是 array 類別array_view 類別array 類別是在建構物件時建立資料的深層複本 (Deep Copy) 的容器類別。 array_view 類別是複製資料的包裝函式類別 (Wrapper Class),當驗證函式存取資料時。 當來源裝置需要資料時資料會被複製回去。

Hh265136.collapse_all(zh-tw,VS.110).gifarray 類別

array 建構物件時,如果您使用包含指向資料集的建構函式,資料的深層複本 (Deep Copy) 將會建立在快速鍵上。 驗證函式修改快速鍵上的複本。 當驗證函式執行完成時,您必須將資料複製到來源資料結構。 下列範例將把向量的每個項目乘以 10。 在驗證函式完成之後, 向量轉換運算子 用來將資料複製回向量物件。

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

Hh265136.collapse_all(zh-tw,VS.110).gifarray_view 類別

array_view 幾乎擁有和 array 類別相同的成員,不過,基礎的行為並不相同。 帶有 array 建構函式時,傳遞到 array_view 建構函式的資料將不會在 GPU 上被複製。 相反地,也就是說,當驗證函式執行時,資料會複製到快速鍵。 因此,如果您建立使用相同資料的兩個 array_view 物件,兩個 array_view 物件會參考相同的記憶體空間。 如果您這麼做,您必須同步任何多執行緒的存取權。 使用 array_view 類別的主要優點是資料只有在必要時才會被移動。

Hh265136.collapse_all(zh-tw,VS.110).gif陣列和 array_view 比較

下表摘要說明 arrayarray_view 類別之間的相似和相異之處。

描述

array 類別

array_view 類別

當層級判斷。

在編譯時期。

在編譯時期。

當範圍判斷。

於執行階段

於執行階段

圖案

矩形

矩形

資料儲存

是資料容器。

是資料的包裝函式。

複製

定義上明確且深層的複本。

被核心函式存取時隱含複本。

資料擷取

藉由複製陣列資料到 CPU 執行緒上的物件。

array_view 的直接存取或呼叫 array_view::synchronize 方法 繼續存取在原始容器的資料。

執行資料的程式碼:parallel_for_each

parallel_for_each 函式定義您要在加速器上執行的程式碼與 arrayarray_view 物件的資料。 請考慮從主題中介紹的程式碼。

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

parallel_for_each 方法會採用兩個引數,估計網域和 Lambda 運算式。

估計網域 是 extent 物件或定義了以平行執行所建立的執行緒 tiled_extent 物件。 計算網域的每個項目都會產生一個執行緒。 在這個案例中, extent 物件是一維的且具有五個項目。 因此,五個執行緒開始。

Lambda 運算式 在每個執行緒定義程式碼執行。 擷取子句, [=],指定 Lambda 運算式的主體是由值存取任何擷取的變數,在這個案例中為 a, b和 sum。 在此範例中,參數清單建立一維 index 名稱為 idx的變數。 idx[0] 在第一個執行緒的值是 0,且每個後續執行緒都會多一。 restrict(amp) 表示 C++ AMP 可以加速使用 C++ 語言的子集。 在具有限制修飾詞的函式的限制將在 限制子句 (C++ AMP)說明。 如需詳細資訊,請參閱Lambda 運算式語法

Lambda 運算式可以包含程式碼執行也可以呼叫個別驗證函式。 驗證函式必須包含 restrict(amp) 修飾詞。 下列範例與前一個範例相同,不過,它會呼叫個別驗證函式。

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

加速程式碼:並排顯示和阻礙

使用並排顯示,您可以取得額外的加速效果。 並排顯示區分執行緒分成兩個矩形的子集或並排。 您可以根據資料集的正確的並排顯示大小和演算法您的程式碼。 對於每個執行緒,您可以存取資料項目的 全域 位置相對於整個 arrayarray_view 路徑的存取 本機 位置相對於並排顯示。 使用本機索引值會簡化程式碼,因為您不必撰寫程式碼轉譯全域的索引值至本機。 使用並排顯示,並於估計網域的 extent::tile 方法parallel_for_each 方法和使用 tiled_index 物件在 Lambda 運算式。

在一般的應用程式,在並排顯示的項目在某方面相關,而且程式碼必須存取和記錄跨並排顯示的值。 使用 tile_static 關鍵字 關鍵字和 tile_barrier::wait 方法 完成這項工作。 具有 tile_static 關鍵字的變數會跨整個並排顯示的範圍和變數的執行個體為每個並排顯示以建立。 您必須處理並排顯示執行緒存取變數的同步處理。 tile_barrier::wait 方法 停止目前執行緒的執行,直到在並排顯示中的所有執行緒到達呼叫 tile_barrier::wait。 您可以使用 tile_static 變數,以便您可以累積跨並排顯示的值。 然後您可以完成需求的任何值的任何計算。

下列圖表代表二維陣列在並排顯示封送處理的取樣資料。

磚狀內容中的索引值

下列程式碼範例會使用上一個圖的取樣資料。 程式碼會以並排顯示的平均值取代並排顯示中的每一個值。

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

數學程式庫

C++ AMP 由兩個數學程式庫。 Concurrency::precise_math 命名空間 的雙精度程式庫為雙精度函式的支援。 它也提供單精確度函式的支援,不過,仍然需要在硬體上的雙精度支援。 其符合 C99 規格 (ISO/IEC 9899)。 快速鍵必須支援完整雙精度。 您可以決定是否要將變更簽入 accelerator::supports_double_precision 資料成員進行。 快速的數學程式庫,在 Concurrency::fast_math 命名空間,包含另一組數學函式。 這些函式,只支援 float 運算元,執行速度更快,但精確度與雙精度浮點數 (Double) 數學程式庫不同。 函式在 <amp_math.h> 的標頭檔 (Header File),而且都以 restrict(amp)宣告。 在 <cmath> 標頭檔 (Header File) 的函式會匯入至 fast_mathprecise_math 命名空間。 restrict 關鍵字用來區別 <cmath> 版本和 C++ AMP 版本。下列程式碼會使用快速的方法計算在計算網域中的每一個值的基底為 10 的對數。

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

圖庫

C++ AMP 為了加速圖形程式設計所設計的圖形程式庫。 這個程式庫支援原生繪圖功能的裝置所使用。 方法會在 Concurrency::graphics 命名空間 和在 <amp_graphics.h> 的標頭檔 (Header File)。 圖庫的主要元件是:

  • texture 類別:您可以使用紋理類別建立紋理從記憶體或從檔案。 紋理類似陣列,因為它們包含資料,因此,它類似容器會使用標準樣板程式庫 (STL) 中有關工作和複製建構函式。 如需詳細資訊,請參閱STL 容器texture 類別樣板參數是元素型別和陣序。 這個層級可以是 1、2 或 3。 項目型別可以是本文稍後說明的其中一個簡短的向量型別。

  • writeonly_texture_view 類別:提供對所有紋理的唯讀存取。

  • 簡短的向量文件庫:會定義以 int、 uint、 float、 double、 準則unorm的一組長度為 2、3 和 4 版中,使用簡短向量型別。

Windows 市集 應用程式

就像其他 C++ 程式庫,您可以在 Windows 市集 應用程式可以使用 C++ AMP。 這些文件在應用程式說明如何將使用 C++、C# 中為,在 Visual Basic 或 JavaScript,所建立的 AMP C++ 程式碼:

C++ AMP 和並行視覺化檢視

並行視覺化檢視是由支援 C++ AMP 分析程式碼效能。 這些文件說明這些功能:

效能建議

不帶正負號的整數模數和分割的帶正負號的整數模數和分割產生較佳的效能。 建議您盡可能使用不帶正負號的整數。

請參閱

參考

Lambda 運算式語法

其他資源

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

參考 (C++ AMP)

機器碼平行程式設計部落格