1. 程式人生 > >C++AMP 遇見C++ AMP 在GPU上做平行計算

C++AMP 遇見C++ AMP 在GPU上做平行計算

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

遇見C++ AMP:在GPU上做平行計算

 

Written by Allen Lee

 

I see all the young believers, your target audience. I see all the old deceivers; we all just sing their song.
– Marilyn Manson, Target Audience (Narcissus Narcosis)

 

從CPU到GPU

      在《遇見C++ PPL:C++的並行和非同步》裡,我們介紹瞭如何使用C++ PPL在CPU上做平行計算,這次,我們會把舞臺換成GPU,介紹如何使用C++ AMP在上面做平行計算。

      為什麼選擇在GPU上做平行計算呢?現在的多核CPU一般都是雙核或四核的,如果把超執行緒技術考慮進來,可以把它們看作四個或八個邏輯核,但現在的GPU動則就上百個核,比如中端的NVIDIA GTX 560 SE就有288個核,頂級的NVIDIA GTX 690更有多達3072個核,這些超多核(many-core)GPU非常適合大規模平行計算。

      接下來,我們將會在《遇見C++ PPL:C++的並行和非同步》的基礎上,對平行計算正弦值的程式碼進行一番改造,使之可以在GPU上執行。如果你沒讀過那篇文章,我建議你先去讀一讀它的第一節。此外,本文也假設你對C++ Lambda有所瞭解,否則,我建議你先去讀一讀《遇見C++ Lambda》

 

平行計算正弦值

      首先,包含/引用相關的標頭檔案/名稱空間,如程式碼1所示。amp.h是C++ AMP的標頭檔案,包含了相關的函式和類,它們位於concurrency名稱空間之內。amp_math.h包含了常用的數學函式,如sin函式,

concurrency::fast_math名稱空間裡的函式只支援單精度浮點數,而concurrency::precise_math名稱空間裡的函式則對單精度浮點數和雙精度浮點數均提供支援。

程式碼 1

      把浮點數的型別從double改成float,如程式碼2所示,這樣做是因為並非所有GPU都支援雙精度浮點數的運算。另外,std和concurrency兩個名稱空間都有一個array類,為了消除歧義,我們需要在array前面加上"std::"字首,以便告知編譯器我們使用的是STL的array類。

程式碼 2

      接著,建立一個array_view物件,把前面建立的array物件包裝起來,如程式碼3所示。array_view物件只是一個包裝器,本身不能包含任何資料,必須和真正的容器搭配使用,如C風格的陣列、STL的array物件或vector物件。當我們建立array_view物件時,需要通過型別引數指定array_view物件裡的元素的型別以及它的維度,並通過建構函式的引數指定對應維度的長度以及包含實際資料的容器。

程式碼 3

      程式碼3建立了一個一維的array_view物件,這個維度的長度和前面的array物件的長度一樣,這個包裝看起來有點多餘,為什麼要這樣做?這是因為在GPU上執行的程式碼無法直接訪問系統記憶體裡的資料,需要array_view物件出來充當一個橋樑的角色,使得在GPU上執行的程式碼可以通過它間接訪問系統記憶體裡的資料。事實上,在GPU上執行的程式碼訪問的並非系統記憶體裡的資料,而是複製到視訊記憶體的副本,而負責把這些資料從系統記憶體複製到視訊記憶體的正是array_view物件,這個過程是自動的,無需我們干預。

      有了前面這些準備,我們就可以著手編寫在GPU上執行的程式碼了,如程式碼4所示。parallel_for_each函式可以看作C++ AMP的入口點,我們通過extent物件告訴它建立多少個GPU執行緒,通過Lambda告訴它這些GPU執行緒執行什麼程式碼,我們通常把這個程式碼稱作Kernel。

程式碼 4

      我們希望每個GPU執行緒可以完成和結果集裡的某個元素對應的一組操作,比如說,我們需要計算10個浮點數的正弦值,那麼,我們希望建立10個GPU執行緒,每個執行緒依次完成讀取浮點數、計算正弦值和儲存正弦值三個操作。但是,每個GPU執行緒執行的程式碼都是一樣的,如何區分不同的GPU執行緒,並定位需要處理的資料呢?

      這個時候就輪到index物件出場了,我們的array_view物件是一維的,因此index物件的型別是index<1>,這個維度的長度是10,因此將會產生從0到9的10個index物件,每個GPU執行緒對應其中一個index物件。這個index物件將會通過Lambda的引數傳給我們,而我們將會在Kernel裡通過這個index物件找到當前GPU執行緒需要處理的資料。

      既然Lambda的引數只傳遞index物件,那Kernel又是如何與外界交換資料的呢?我們可以通過閉包捕獲當前上下文的變數,這使我們可以靈活地操作多個數據源和結果集,因此沒有必要提供返回值。從這個角度來看,C++ AMP的parallel_for_each函式在用法上類似於C++ PPL的parallel_for函式,如程式碼5所示,我們傳給前者的extent物件代替了我們傳給後者的起止索引值。

程式碼 5

      那麼,Kernel右邊的restrict(amp)修飾符又是怎麼一回事呢?Kernel最終是在GPU上執行的,不管以什麼樣的形式,restrict(amp)修飾符正是用來告訴編譯器這點的。當編譯器看到restrict(amp)修飾符時,它會檢查Kernel是否使用了不支援的語言特性,如果有,編譯過程中止,並列出錯誤,否則,Kernel會被編譯成HLSL,並交給DirectCompute執行。Kernel可以呼叫其他函式,但這些函式必須新增restrict(amp)修飾符,比如程式碼4的sin函式

      計算完畢之後,我們可以通過一個for迴圈輸出array_view物件的資料,如程式碼6所示。當我們在CPU上首次通過索引器訪問array_view物件時,它會把資料從視訊記憶體複製回系統記憶體,這個過程是自動的,無需我們干預。

程式碼 6

      哇,不知不覺已經講了這麼多,其實,使用C++ AMP一般只涉及到以下三步:

  1. 建立array_view物件。
  2. 呼叫parallel_for_each函式。
  3. 通過array_view物件訪問計算結果。

其他的事情,如視訊記憶體的分配和釋放、GPU執行緒的規劃和管理,C++ AMP會幫我們處理的。

 

平行計算矩陣之和

      上一節我們通過一個簡單的示例瞭解C++ AMP的使用步驟,接下來我們將會通過另一個示例深入瞭解array_view、extent和index在二維場景裡的用法。

      假設我們現在要計算兩個100 x 100的矩陣之和,首先定義矩陣的行和列,然後通過create_matrix函式建立兩個vector物件,接著建立一個vector物件用於存放矩陣之和,如程式碼7所示。

程式碼 7

      create_matrix函式的實現很簡單,它接受矩陣的總容量(行和列之積)作為引數,然後建立並返回一個包含100以內的隨機數的vector物件,如程式碼8所示。

程式碼 8

      值得提醒的是,當create_matrix函式執行"return matrix;"時,會把vector物件拷貝到一個臨時物件,並把這個臨時物件返回給呼叫方,而原來的vector物件則會因為超出作用域而自動銷燬,但我們可以通過編譯器的Named Return Value Optimization對此進行優化,因此不必擔心按值返回會帶來效能問題。

      雖然我們通過行和列等二維概念定義矩陣,但它的實現是通過vector物件模擬的,因此在使用的時候我們需要做一下索引變換,矩陣的第m行第n列元素對應的vector物件的索引是m * columns + n(m、n均從0開始計算)。假設我們要用vector物件模擬一個3 x 3的矩陣,如圖1所示,那麼,要訪問矩陣的第2行第0列元素,應該使用索引6(2 * 3 + 0)訪問vector物件。

圖 1

      接下來,我們需要建立三個array_view物件,分別包裝前面建立的三個vector物件,建立的時候先指定行的大小,再指定列的大小,如程式碼9所示。

程式碼 9

      因為我們建立的是二維的array_view物件,所以我們可以直接使用二維索引訪問矩陣的元素,而不必像前面那樣計算對應的索引。還是以3 x 3的矩陣為例,如圖2所示,vector物件會被分成三段,每段包含三個元素,第一段對應array_view物件的第一行,第二段對應第二行,如此類推。如果我們想訪問矩陣的第2行第0列的元素,可以直接使用索引 (2, 0) 訪問array_view物件,這個索引對應vector物件的索引6。

圖 2

      考慮到第一、二個array_view物件的資料流動方向是從系統記憶體到視訊記憶體,我們可以把它們的第一個型別引數改為const int,如程式碼10所示,表示它們在Kernel裡是隻讀的,不會對它包裝的vector物件產生任何影響。至於第三個array_view物件,由於它只是用來輸出計算結果,我們可以在呼叫parallel_for_each函式之前呼叫array_view物件的discard_data成員函式,表明我們對它包裝的vector物件的資料不感興趣,不必把它們從系統記憶體複製到視訊記憶體。

程式碼 10

      有了這些準備,我們就可以著手編寫Kernel了,如程式碼11所示。我們把第三個array_view物件的extent傳給parallel_for_each函式,由於這個矩陣是100 x 100的,parallel_for_each函式會建立10,000個GPU執行緒,每個GPU執行緒計算這個矩陣的一個元素。由於我們訪問的array_view物件是二維的,索引的型別也要改為相應的index<2>。

程式碼 11

      看到這裡,你可能會問,GPU真能建立這麼多個執行緒嗎?這取決於具體的GPU,比如說,NVIDIA GTX 690有16個多處理器(Kepler架構,每個多處理器有192個CUDA核),每個多處理器的最大執行緒數是2048,因此可以同時容納最多32,768個執行緒;而NVIDIA GTX 560 SE擁有9個多處理器(Fermi架構,每個多處理器有32個CUDA核),每個多處理器的最大執行緒數是1536,因此可以同時容納最多13,824個執行緒。

      計算完畢之後,我們可以在CPU上通過索引器訪問計算結果,程式碼12向控制檯輸出結果矩陣的第14行12列元素。

程式碼 12

 

async + continuation

      掌握了C++ AMP的基本用法之後,我們很自然就想知道parallel_for_each函式會否阻塞當前CPU執行緒。parallel_for_each函式本身是同步的,它負責發起Kernel的執行,但不會等到Kernel的執行結束才返回。以程式碼13為例,當parallel_for_each函式返回時,即使Kernel的執行還沒結束,checkpoint 1位置的程式碼也會照常執行,從這個角度來看,parallel_for_each函式是非同步的。但是,當我們通過array_view物件訪問計算結果時,如果Kernel的執行還沒結束,checkpoint 2位置的程式碼會卡住,直到Kernel的執行結束,array_view物件把資料從視訊記憶體複製到系統記憶體為止。

程式碼 13

      既然Kernel的執行是非同步的,我們很自然就會希望C++ AMP能夠提供類似C++ PPL的continuation。幸運的是,array_view物件提供一個synchronize_async成員函式,它返回一個concurrency::completion_future物件,我們可以通過這個物件的then成員函式實現continuation,如程式碼14所示。事實上,這個then成員函式就是通過C++ PPL的task物件實現的。

程式碼 14

 

你可能會問的問題

      1. 開發C++ AMP程式需要什麼條件?

      你需要Visual Studio 2012以及一塊支援DirectX 11的顯示卡,Visual C++ 2012 Express應該也可以,如果你想做GPU除錯,你還需要Windows 8作業系統。執行C++ AMP程式需要Windows 7/Windows 8以及一塊支援DirectX 11的顯示卡,部署的時候需要把C++ AMP的執行時(vcamp110.dll)放在程式可以找到的目錄裡,或者在目標機器上安裝Visual C++ 2012 Redistributable Package

      2. C++ AMP是否支援其他語言?

      C++ AMP只能在C++裡使用,其他語言可以通過相關機制間接呼叫你的C++ AMP程式碼:

      3. C++ AMP是否支援其他平臺?

      目前C++ AMP只支援Windows平臺,不過,微軟釋出了C++ AMP開放標準,支援任何人在任何平臺上實現它。如果你希望在其他平臺上利用GPU做平行計算,你可以考慮其他技術,比如NVIDIA的CUDA(只支援NVIDIA的顯示卡),或者OpenCL,它們都支援多個平臺。

      4. 能否推薦一些C++ AMP的學習資料?

      目前還沒有C++ AMP的書,Kate Gregory和Ade Miller正在寫一本關於C++ AMP的書,希望很快能夠看到它。下面推薦一些線上學習資料:

 

*宣告:本文已經首發於InfoQ中文站,版權所有,《遇見C++ AMP:在GPU上做平行計算》,如需轉載,請務必附帶本宣告,謝謝。

http://www.cnblogs.com/allenlooplee/archive/2012/08/15/2640644.html

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述