微軟發表用於 GPU 大量平行計算的 C++ AMP


自從 nVIDIA CUDA 推出後,把顯示卡的繪圖晶片拿來做通用計算的 GPGPU 算是用來越普及了~目前除了 CUDA 外,AMD 也有自己的 Stream SDK(目前以改名為 AMD APP),同時也更有跨硬體的標準 OpenCL 以及微軟 DirectX 裡的 DirectCompute(MSDN)。

而微軟在這幾天 AMD Fusion Developer Summit  上,也發表了一個用於 C++ 的 GPU 大量平行化的開發技術,命名為「C++ Accelerated Massive Parallelism」。目前似乎還沒有能找到詳細資料,不過就現在看到的資料,重點是在於:

  • STL-Like、C++ 的語法,只要會用 STL 就會用。也就是他的學習門檻會更低~
  • 會整合在下一版的 Visual C++ 中,不需要額外的編譯器也不需要其他的語法。
  • 基於微軟的 DirectCompute(這代表了他應該只能用在 Windows Vista / Windows 7)。
  • 將可以使用在 GPU,以及 APU 等適合大量平行計算(massively parallel)的硬體上。

詳細的資料可以參考《C++ Accelerated Massive Parallelism》和《Introducing C++ Accelerated Massive Parallelism (C++ AMP)》,不過說實話細節也不多。另外,在 Channel 9 上可以找到展示的影片(網頁簡報投影片),裡面有簡單的矩陣相乘範例程式碼;而在《Microsoft brings GPU computing to C++ with C++ AMP》一文中,也有一些簡單的說明。

其中,在《Microsoft brings GPU computing to C++ with C++ AMP》一文中可以看到,C++ AMP 主要是在 C++ 裡加上了一些必要的新的 class,包括了:array<T, N>array_view<T, N>index<N>grid<N>tiled_grid<Z, Y, X> 等等。

另外,也針對平行化處理的需求,加上了 parallel_for_each() 的函式,以及額外的 restrict(direct3d, cpu) 指令。

在投影片中所提供的「Hello World」範例,則是簡單的矩陣相加~如果直接用 C++ 寫的話,會是:

void AddArrays( int n, int* pA, int* pB, int* pC )
{
  for( int i = 0; i < n; ++ i )
  {
    pC[i] = pA[i] + pB[i];
  }
}

如果將這段程式用 C++ AMP 改寫的話,則會變成:

#include <amp.h>
using namespace concurrency;
 
void AddArrays( int n, int* pA, int* pB, int* pC )
{
  array_view< int, 1 > a( n, pA );
  array_view< int, 1 > b( n, pB );
  array_view< int, 1 > sum( n, pC );
 
  parallel_for_each(
    sum.grid,
[=]( index<1> idx ) restrict(direct3d) { sum[idx] = a[idx] + b[idx]; }
); }

而在影片(網頁)裡面的範例程式碼,則是提供了另一個、稍微複雜一點的矩陣相乘的程式碼當作例子。用 C++ 寫的話,會是:

void MatrixMult( float* C, const vector<float>& A, const vector<float>& B,
                 int M, int N, int W)
{
  for( int y = 0; y < M; y++ )
    for( int x = 0; x < N; x++ )
    {
      float sum = 0;
      for( int i = 0; i < W; i++ )
        sum += A[y*W+i] * B[i*N+x];
      C[y*N+x] = sum;
    }
}

改使用 C++ AMP 則會變成(還要加上 #include <amp.h>、且部分的型別的 namespace 是 concurrency):

void MatrixMult( float* C, const vector<float>& A, const vector<float>& B,
                 int M, int N, int W)  
{
  array_view<const float, 2> a(M,W,A), b(W,N,B);
  array_view<writeonly<float>, 2> c(M,N,C);

  parallel_for_each( c.grid, [=](index<2> idx) restrict(direct3d){
    float sum = 0;
    for( int i = 0; i < a.x; i++ )
      sum += a(idx.y,i)*b(I,idx.x);
    c[idx] = sum;  } );
}

以上面兩個範例看來,C++ AMP 的平行計算方法,目前似乎只有一個 parallel_for_each()?而在使用上,的確真的很接近 STL 的用法~要使用時,應該就是:

  1. 加上 <amp.h> 這個 header 檔、並指定 namespace(concurrency 現在是微軟 PPL 的 namespace)。
  2. 將資料轉換為 C++ AMP 的特定型別(這邊都是用 array_view
  3. 將實際要執行的程式以 function object 的形式,和資料一起丟給 parallel_for_each(),做平行化的處理。

在這兩個範例裡,都是使用 C++0x 的 lambda expression(參考)的形式來建構所需要的 function object;而其中比較特別的是,對於這個 function object 他還加上了 restrict(direct3d) 這樣的程式碼。雖然以目前的資料還說還不是很確定,不過 restrict() 這個指令應該是用來指定這個 function object 丟到 parallel_for_each() 後,是要在 CPU 還是 GPU 上執行了~


在 Heresy 來看,這樣的語法如果和 nVIDIA CUDA 或是 OpenCL 來比的話,確實是又再進一步簡化不少了!基本上要做的,就是轉換成 C++ AMP 特定的資料型態、然後呼叫 parallel_for_each() 就好了~資料到底在 CPU 還是在 GPU 上,應該也是由 C++ AMP 自己負責處理掉了(可以在轉換成 array_view 時,以 constwriteonly 來做一定程度的控制);而就像範例裡面一樣,再搭配 lambda expression 的話,更是連 kernel function 都不用額外定義了~如此一來,就真的很像在寫一般的 C++ STL algorithm 的迴圈程式,雖然比不上 OpenMP,但是也是相當地簡單了!

不過如此一來,也代表大部分的 GPU 配置動作都是交給編譯器自動決定、處理了,所以接下來要擔心的,可能就是編譯器在自動最佳化方面的效率(包括了記憶體在 CPU / GPU 間的複製、以及平行化的工作配置)了~

另外,由於這項功能是基於微軟 DirectX 11 中的 DirectCompute 來實作的,所以這也代表了 C++ AMP 這項技術也會被綁在 Windows Vista / Windows 7 以後的作業系統上,而不像 CUDA、OpenCL 可以跨平台了…這點對於要開發跨平台程式的人來說,就覺得比較悲哀了。

不過由於微軟是把 C++AMP 當作一個開放規格(open specification),所以理論上其他編譯器應該也可以自己支援 C++ AMP 的語法。而如果 restrict() 可以有 direct3dcpu 以外的選項的話,那其他編譯器應該也可以用 DirectCompute 以外的方法來實作(例如 OpenCL),這樣應該就可以跨平台支援了。不過這樣一來,重點就是有多少編譯器願意支援了。

而由於這項功能是會整合在下一版的 Visual Studio 裡,所以看來短時間內應該都還沒有辦法玩到了。現在,也就只能慢慢期待,微軟趕快推出有這項功能的產品囉~

對「微軟發表用於 GPU 大量平行計算的 C++ AMP」的想法

回覆給Kun-Yi 取消回覆

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料