程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++ AMP異構並行編程解析

C++ AMP異構並行編程解析

編輯:C++入門知識

微軟在今年2月份的GoingNative大會上正式對外發布了C++ AMP(Accelerated Massive Parallelism)開放規范。C++ AMP是微軟於11年6月推出的一個異構並行編程框架,從Visual Studio 11開發者預覽版起,微軟正式提供了C++AMP的支持。C++ AMP的目標是降低在由CPU和GPU共同組成的異構硬件平台上進行數據並行編程(data parallel)的門檻。通過C++ AMP,開發者將獲得一個類似C++ STL的庫,這個庫將作為微軟concurrency namespace的一部分,開發者既不需要學習新的C++語法,也不需要更換編譯器就能夠方便地進行異構並行編程。本文主要介紹C++ AMP的設計原則和語法規則,並將其與CUDA和OpenCL這兩個已有的異構並行編程框架進行了對比,希望對大家了解異構並行編程有所幫助。
C++ AMP設計原則
隨著CPU由單核向多核轉移,多核計算成為了近幾年的熱點。另一方面,GPU編程也經歷著一場變革。傳統意義上,GPU一直是作為圖形圖像專用處理器而存在。然後,因為GPU擁有比CPU還要強大的浮點並行運算能力,我們是不是能讓GPU來完成一些通用的計算任務呢?答案是肯定的,例如科學計算中就需要大量的用到浮點計算。在這樣的背景下,我們可以將並行計算從單純的在多核CPU上做,擴展到在多核CPU與GPU共同組成的異構硬件平台上來。除了多核與GPU通用計算的快速發展職位,雲計算更成為軟件開發的一個重要趨勢。實際上,雲端的每一台服務器都可以是由多核CPU和GPU共同組成的異構硬件平台。微軟的Herb Sutter介紹說:“我們認為多核編程、GPU編程和雲計算根本不是三個獨立的趨勢。實際上,他們只是同一種趨勢的不同方面,我們把這個趨勢叫做異構並行編程”。進行異構並行編程需要一個統一的編程模型,這就是微軟推出C++ AMP的原因。
微軟決定另起爐灶,推出C++ AMP這樣一個全新的異構並行編程模型的原因很簡單,他們認為這個編程模型必須同時具備下面這六個特征,而目前已有的CUDA和OpenCL並不同時滿足這些需求。
• C++ 而不是 C :這種編程模型應該利用好C++豐富的語言特性(例如抽象,模板,例外處理等),並且不會犧牲性能,因此我們不能像OpenCL一樣只是C語言的一種方言;
• 主流 : 這個編程框架應該能被成千上萬的開發者所使用,而不是只被少數人所接受。一個立見分曉的檢驗辦法是:用該編程框架實現GPU上的hello world是只需要幾行代碼,還是需要幾十行才行?
• 最小的改動 : 這個編程模型應該只需要在C++上進行最小的改動就能夠實現應有的功能。通過一個非常小的、具有良好設計的語言擴展,我們就可以把絕大部分復雜的實現交由運行時系統/庫去完成。
• 可移植的。 這種編程模型應該讓用戶只需要一個二進制可執行文件就可以在任何廠商的GPU硬件上面運行。目前我們使用Direct Compute來實現Windows上所有支持DX11的 GPU上的C++ AMP編程模型,但是未來我們會根據用戶的需求在其他異構硬件平台上做相應的實現。
• 通用且不會過時 。C++ AMP目前針對的是GPU並行計算。但是我們希望,將來C++ AMP的程序可以無縫的擴展到其他形式的計算單元上去,例如FPGA,雲端的CPU/GPU處理器等等。
• 開放 。微軟將吧C++ AMP做成一個開放標准,我們鼓勵第三方在任何硬件和操作系統上實現C++ AMP編譯器和運行時系統。目前AMD和Nvidia都已經聲明將會支持C++ AMP。
C++ AMP介紹
下面讓我們通過一個簡單的程序來了解一下C++ AMP的一些語法規則。首先我們需要引用amp.h這個頭文件。C++ AMP中的模板都在concurrency這個命名空間內,所以也需要引用。在C++ AMP中主要有array和array_view這兩種數據容器。這兩者主要的區別在於array類型的數據在創建時會在GPU顯存上擁有一個備份,在GPU對該數據進行完運算之後,開發者必須手動將數據拷貝回CPU。與之相比,array_view其實是一個數據結構的封裝,只有在它指向的數據被GPU調用時才會被拷貝到GPU上進行相應的計算。從下例中我們看到,聲明array_view數據時需要提供兩個模板參數:array_view元素的類型和數據結構的緯度。因為aCPP,bCPP和sumCPP都是一維數組,因此我們在聲明時傳入int和1兩個參數。
接下來就是最重要的計算部分了。parallel_for_each這個方法就是執行在GPU部分的代碼的入口。可以看到,parallel_for_each有兩個參數,第一個名為sum.extent的參數是用於描述並行計算拓撲結構的對象。通過這個變量,我們指定有多少個GPU線程來並行執行該計算任務,以及這些線程的排列方式。Sum.extend可以理解為按照sum的數據緯度來分配相應數目的GPU線程。Parallel_for_each的第二個參數是一個名為“[=] (index<1> idx) restrict(amp)”的lambda表達式。方括號裡的“=”代表了表示lambda表達式的捕獲列表。具體來說,“[=]”表示lambda裡捕捉的變量按照傳值的方式來引用。該for循環的主要參數就是index<1> idx了,它其實代表的是GPU線程的編號。因為之前我們已經通過sum.extent定義好了GPU線程的數量和拓撲結構,因此這個index參數代表的就是一維的數組,即從0到4共5個數。最後一個參數restrict(amp)用來表示parallel_for_each的函數體運行在默認GPU設備上。當然我們也可以定義出amp之外的其他的語法約束,具體的內容請大家參考[1]中的內容。在這之後就是循環體了。這個例子的循環體非常簡單,就是讓GPU用5個線程並行地把數組a和b中的元素依次相加並存到sum數組中去。
#include <amp.h>
#include <iostream>
using namespace concurrency;

void CampMethod() {
    int aCPP[] = {1, 2, 3, 4, 5};
    int bCPP[] = {6, 7, 8, 9, 10};
    int sumCPP[5] = {0, 0, 0, 0, 0};

    // Create C++ AMP objects.
    array_view<int, 1> a(5, aCPP);
    array_view<int, 1> b(5, bCPP);
    array_view<int, 1> sum(5, sumCPP);

    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 < 5; i++) {
        std::cout << sum[i] << "\n";
    }
}
從這個例子我們可以看到,使用C++ AMP進行異構多線程編程確實是很容易的。開發者如果熟悉C++的話,一般只需要很短的時間就可以上手實現相應的功能。
CUDA、OpenCL與C++ AMP
其實在C++ AMP之前已經有了兩個異構編程框架:CUDA與OpenCL。CUDA(Compute Unified Device Architecture)是顯卡廠商Nvidia於2007年推出的業界第一款異構並行編程框架。在Nvidia的大力支持下,CUDA擁有良好的開發環境,豐富的函數庫,優秀的性能。但是CUDA只能被用於在Nvidia的顯卡上進行異構編程,有先天的局限性。OpenCL (Open Computing Language) 是業界第一個跨平台的異構編程框架。它是Apple領銜並聯合Nvidia,AMD,IBM,Intel等眾多廠商於2008年共同推出的一個開放標准,由單獨成立的非營利性組織Khronos Group管理。與C++ AMP類似,OpenCL作為一個開放的標准,並不局限於某個特定的GPU廠商,從這點上來看,Nvidia自己獨家的CUDA顯得很封閉。我們可以把OpenCL在異構編程上的地位與OpenGL和OpenAL類比,這兩個標准分別用於三維圖形和計算機音頻。
因為CUDA與OpenCL比C++AMP更接近硬件底層,所以前兩者的性能更好,然而與C++ AMP的易編程性卻要優於CUDA和OpenCL。與C++ AMP基於C++語言特性直接進行擴展不同,OpenCL是基於C99編程語言進行的相關修改和擴展,因此C++ AMP比OpenCL擁有更高層次的抽象,編程更加簡單。在CUDA和OpenCL中,kernels(運行在GPU上的代碼)必須被封裝成特定函數,而在C++ AMP中,代碼看起來整潔的多:我們只需要使用for循環中內嵌的lambda函數就能完成異構並行計算,而且它的內存模型也在一定程度上被大大簡化了。
那麼在OpenCL、CUDA 與C++ AMP之間,開發者該如何選擇呢?
1)  如果你只需要在Windows平台上進行異構編程,並且看重易編程性的話,C++ AMP無疑是最好的選擇。依托於Visual Studio這個強有力的開發工具,再加上基於C++這一更高層抽象帶來的先天優勢,C++ AMP將為Windows開發者進行異構編程提供良好的支持。
2)  如果你只需要在Nvidia的GPU卡上進行異構編程,並且非常看重性能的話,CUDA應該是第一選擇:在Nvidia的強力支持下,CUDA在Nvidia硬件上的性能一直保持領先,許多學術研究表明OpenCL與CUDA的性能相差不大,在一部分應用中CUDA的性能稍微好於OpenCL。同時CUDA的開發環境也非常成熟,擁有眾多擴展函數庫支持。
3)  如果你更注重不同平台間的可移植性,OpenCL可能是目前最好的選擇。作為第一個異構計算的開放標准,OpenCL已經得到了包括Intel,AMD,Nvidia,IBM,Oracle,ARM,Apple,Redhat等眾多軟硬件廠商的大力支持。當然,C++ AMP本身也是一個開放的標准,只是目前只有微軟自己做了實現,將來C++ AMP的跨平台支持能做到什麼程度還是一個未知數。
其實從編程語言的發展來看,易編程性往往比性能更加重要。從Java和.Net的流行,到腳本語言的崛起,編程效率無疑是最重要的指標。更不用說開發者往往可以通過更換下一代GPU硬件來獲得更好的性能。從這點來看,C++ AMP通過降低異構編程的編程難度,實際上也是推進了異構編程的普及。下面我們需要看的就是C++ AMP是否能成為真正的業界標准,而不僅僅局限於微軟自己的平台,微軟這次開放C++ AMP標准的行為也正是為了推廣C++ AMP在業界的普及。
總結
目前整個業界的異構硬件體系結構仍然處於快速演變之中。可以看到,許多廠商的處理器正在嘗試融合CPU和GPU(例如AMD的Fusion,Intel的Larrabee和Nvidia的Tegra3都融合了CPU和GPU)。如果將來的處理器上集成了CPU和GPU,並通過同一條總線使它們與內存直接相連的話,我們就不需要向今天這樣把數據在CPU和GPU之間搬來搬去了。隨著異構硬件的發展,與之相對應的異構編程框架在需要隨著演變。可以預見,今天我們看到的CUDA,OpenCL和C++ AMP都只處於一個初期形態,將來它們還會有很多新的變化。但是有一點我們可以肯定:將來的異構編程一定會比現在更加容易。

 


作者 Guancheng

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved