C++ AMP 概觀
注意
從 Visual Studio 2022 17.0 版開始,C++ AMP 標頭已被取代。
包含任何 AMP 標頭將會產生建置錯誤。 先定義 _SILENCE_AMP_DEPRECATION_WARNINGS
,再包含任何 AMP 標頭以讓警告無聲。
C++加速大規模平行處理原則(C++ AMP)利用離散圖形處理器(GPU)等數據平行硬體,加速執行C++程序代碼。 藉由使用 C++ AMP,您可以撰寫多維度數據演算法的程式代碼,以便在異質硬體上使用平行處理原則來加速執行。 C++ AMP 程式設計模型包含多維陣列、索引、記憶體傳輸、並排顯示和數學函式庫。 您可以使用 C++ AMP 語言延伸模組來控制數據從 CPU 移至 GPU 和返回的方式,以便改善效能。
系統需求
Windows 7 或更新版本
Windows Server 2008 R2 到 Visual Studio 2019。
DirectX 11 功能層級 11.0 或更新版本硬體
若要在軟體模擬器上進行偵錯,需要 Windows 8 或 Windows Server 2012。 如需在硬體上偵錯,您必須安裝圖形卡的驅動程式。 如需詳細資訊,請參閱偵錯 GPU 程式碼。
注意:ARM64 目前不支援 AMP。
簡介
下列兩個範例說明 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";
}
}
程序代碼的重要部分如下:
數據:數據是由三個陣列所組成。 所有人都有相同的排名(一)和長度(五)。
反覆專案:第一
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++陣列來建構三個C++ AMP array_view 物件。 您會提供四個
array_view
值來建構對象:數據值、排名、元素類型,以及每個維度中對象的長度array_view
。 排名和類型會以類型參數的形式傳遞。 數據和長度會以建構函式參數的形式傳遞。 在此範例中,傳遞至建構函式的C++陣列是一維。 排名和長度可用來建構 物件中array_view
數據的矩形圖形,而數據值則用來填滿數位。 運行時間連結庫也包含 數位類別,其具有類似 類別的array_view
介面,本文稍後會討論。反覆 專案:parallel_for_each函式 (C++ AMP) 提供逐一查看數據元素或 計算網域的機制。 在此範例中,計算網域是由 指定
sum.extent
。 您想要執行的程式代碼包含在 Lambda 運算式或 核心函式中。restrict(amp)
表示只會使用C++ AMP 可以加速C++語言的子集。Index: index Class 變數會
idx
以一個等級宣告,以符合物件的排名array_view
。 藉由使用索引,您可以存取 物件的個別專案array_view
。
成形和編製索引數據:索引和範圍
您必須先定義數據值,並宣告數據的形狀,才能執行核心程序代碼。 所有數據都定義為陣列(矩形),而且您可以定義數位以具有任何排名(維度數目)。 數據可以是任何維度中的任何大小。
index 類別
索引類別會藉由將每個維度中原點的位移封裝成一個物件,以指定 或 array_view
物件中array
的位置。 當您存取陣列中的位置時,您會將 對象傳遞 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
下列範例會建立一個三維索引,指定深度 = 0、數據列 = 1,以及三維 array_view
對象中的數據行 = 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
extent 類別
範圍類別會指定 或 array_view
物件之每個維度array
中的數據長度。 您可以建立範圍,並用它來建立 array
或 array_view
物件。 您也可以擷取現有 array
或 array_view
物件的範圍。 下列範例會在物件的每個維度 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";
下列範例會建立物件 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_view 類別。 類別 array
是容器類別,會在建構 物件時建立數據的深層複本。 類別 array_view
是包裝函式類別,會在核心函式存取數據時複製數據。 當來源裝置上需要數據時,數據會複製回去。
array 類別
array
建構物件時,如果您使用包含數據集指標的建構函式,則會在加速器上建立數據的深層複本。 核心函式會修改加速器上的複本。 當核心函式執行完成時,您必須將資料複製到源數據結構。 下列範例會將向量中的每個元素乘以 10。 完成核心函式之後, vector conversion operator
會使用 將數據複製回向量物件。
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 類別
array_view
與類別幾乎具有相同的成員array
,但基礎行為並不相同。 傳遞至 array_view
建構函式的數據不會在 GPU 上複寫,因為它與建構函式一 array
樣。 相反地,當核心函式執行時,數據會複製到加速器。 因此,如果您建立兩個 array_view
使用相同的數據的物件,這兩個 array_view
對象都會參考相同的記憶體空間。 當您這樣做時,您必須同步處理任何多線程存取。 使用 array_view
類別的主要優點是數據只有在必要時才移動。
陣列和array_view的比較
下表摘要說明 和 array_view
類別之間的array
相似性和差異。
描述 | array 類別 | array_view 類別 |
---|---|---|
判斷排名時 | 在編譯時期。 | 在編譯時期。 |
判斷範圍時 | 在運行時間。 | 在運行時間。 |
圖形 | 矩形的。 | 矩形的。 |
資料存放區 | 這是數據容器。 | 這是數據包裝函式。 |
複本 | 定義時的明確和深層複製。 | 核心函式存取隱含複製時。 |
資料擷取 | 將陣列資料複製到 CPU 線程上的物件。 | 藉由直接存取 array_view 物件或呼叫 array_view::synchronize 方法 ,以繼續存取原始容器上的數據。 |
具有陣列和array_view的共享記憶體
共用記憶體是 CPU 和加速器可以存取的記憶體。 使用共用記憶體可消除或大幅減少在 CPU 與加速器之間複製數據的額外負荷。 雖然記憶體已共用,但 CPU 和加速器無法同時存取記憶體,因此會導致未定義的行為。
array
如果相關聯的加速器支持共用記憶體,物件可用來指定使用共用記憶體的精細控制。 加速器是否支持共用記憶體是由加速器的 supports_cpu_shared_memory 屬性所決定,當支援共用記憶體時,就會傳回 true
該屬性。 如果支援共用記憶體,則加速器上記憶體配置的預設 access_type列舉 是由 default_cpu_access_type
屬性所決定。 根據預設,array
和 array_view
物件會接受與主要相關聯的 accelerator
相同 access_type
。
藉由明確設定 的 array::cpu_access_type Data Member 屬性 array
,您可以根據其計算核心的記憶體存取模式,對共用記憶體的使用方式執行精細的控制,以便根據硬體的效能特性優化應用程式。 array_view
會cpu_access_type
反映與其array
相關聯的 ,或者,如果建構array_view沒有數據源,則其access_type
會反映第一次導致它配置記憶體的環境。 也就是說,如果第一次由主機存取 (CPU),則其行為就像是透過 CPU 數據源建立,並透過擷取共用access_type
相關聯的 ;不過,如果第一次由 accelerator_view
存取,則其行為就像是透過該建立的 建立而accelerator_view
array
建立,並共用 的 。accelerator_view
access_type
array
下列程式代碼範例示範如何判斷預設加速器是否支援共用記憶體,然後建立數個具有不同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);
}
透過資料執行程式代碼:parallel_for_each
parallel_for_each函式會定義您要對 array
或 array_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
。 在此範例中,參數清單會建立名為idx
的一維index
變數。 idx[0]
的值在第一個線程中為0,並在每個後續線程中增加一個。 restrict(amp)
表示只會使用C++ AMP 可以加速C++語言的子集。 限制修飾詞的函式限制會在 restrict (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";
}
}
加速程式代碼:磚和屏障
您可以使用並排來取得額外的加速。 並排會將線程分割成相等的矩形子集或 磚。 您可以根據數據集和您正在撰寫的演算法來判斷適當的磚大小。 對於每個線程,您可以存取相對於整體array
的數據元素全域位置,或array_view
存取相對於磚的本機位置。 使用本機索引值可簡化程式碼,因為您不需要撰寫程式代碼,即可將索引值從全域轉譯為本機。 若要使用並排,請在 方法中的parallel_for_each
計算網域上呼叫 extent::tile 方法,並在 Lambda 運算式中使用 tiled_index 物件。
在一般應用程式中,磚中的元素會以某種方式相關,而且程式代碼必須存取並追蹤磚中的值。 使用 tile_static Keyword 關鍵詞和 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::p recise_math 命名空間中的雙精確度連結庫支援雙精確度函式。 它也提供單精度函式的支持,不過仍需要硬體上的雙精確度支援。 它符合 C99 規格(ISO/IEC 9899)。 快速鍵必須支援完整雙精確度。 您可以藉由檢查 accelerator::supports_double_precision Data Member 的值來判斷它是否執行。 Concurrency::fast_math Namespace 中的快速數學連結庫包含另一組數學函式。 這些函式僅 float
支援操作數,執行得更快,但不像雙精確度數學連結庫中的函式那麼精確。 函式包含在 <amp_math.h> 頭檔中,且所有函式都會使用 restrict(amp)
宣告。 cmath> 頭檔中的<函式會同時匯入 fast_math
和 precise_math
命名空間。 關鍵詞 restrict
可用來區分 <cmath> 版本和 C++ AMP 版本。 下列程式代碼會使用fast方法計算計算定義域中每個值的base-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(numbers[idx]);
}
);
for (int i = 0; i < 6; i++) {
std::cout << logs[i] << "\n";
}
}
圖形庫
C++ AMP 包含專為加速圖形程式設計設計的圖形連結庫。 此連結庫僅適用於支援原生圖形功能的裝置。 方法位於 Concurrency::graphics Namespace 中,並包含在 <amp_graphics.h> 頭檔中。 圖像庫的主要元件包括:
texture 類別:您可以使用紋理類別,從記憶體或檔案建立紋理。 紋理類似數位,因為它們包含數據,而且它們與指派和複製建構相關的C++標準連結庫中的容器類似。 如需詳細資訊,請參閱 C++ 標準程式庫容器。 類別的
texture
範本參數是元素類型和排名。 排名可以是 1、2 或 3。 元素類型可以是本文稍後所述的其中一個簡短向量型別。writeonly_texture_view類別:提供任何紋理的僅限寫入存取權。
短向量連結庫:定義一組長度為 2、3 和 4 的短向量類型,其以
int
、uint
、float
、、double
、 norm 或 unorm 為基礎。
通用 Windows 平台 (UWP) 應用程式
如同其他C++連結庫,您可以在UWP app中使用 C++ AMP。 這些文章說明如何在使用 C++、C#、Visual Basic 或 JavaScript 建立的應用程式中包含 C++ AMP 程式代碼:
C++ AMP 和並行可視化檢視
並行可視化檢視包含分析 C++ AMP 程式代碼效能的支援。 這些文章描述這些功能:
效能建議
無符號整數的模數和除法效能明顯優於帶正負號整數的模數和除法。 建議您盡可能使用不帶正負號的整數。
另請參閱
C++ AMP (C++ Accelerated Massive Parallelism)
Lambda 運算式語法
參考 (C++ AMP)
原生程式代碼部落格中的平行程序設計