,
(中原工學(xué)院,鄭州 450007)
近半個(gè)世紀(jì)以來,處理器的計(jì)算速度基本上每18到24個(gè)月都要提升一倍。然而,由于單核處理器功耗和散熱等問題的存在,通過提升芯片時(shí)鐘頻率來無限制地提升性能已不再可能,于是科學(xué)家們又開始了對多核、眾核處理器技術(shù)的探索。
多核芯片是通過在單個(gè)芯片上集成多個(gè)核心而成的,但這種架構(gòu)受到物理規(guī)律的限制,即受到功耗、互連線延時(shí)和設(shè)計(jì)復(fù)雜性等因素的制約。在這種背景下,眾核[1]應(yīng)運(yùn)而生。所謂眾核,是指在單個(gè)處理器中集成多個(gè)簡單的處理器核,即指芯片中擁有大于等于8個(gè)核心的處理器。與單核和多核處理器相比,眾核處理器計(jì)算資源密度更高,片上通信開銷更低,更多的晶體管和能量可以勝任更為復(fù)雜的并行處理應(yīng)用[2]。
從存儲方式上劃分,并行編程模型分為共享存儲模型和分布式存儲模型。而在多核處理器的實(shí)際系統(tǒng)中,通用計(jì)算又造就了異構(gòu)并行編程模型。
本文就基于多核和眾核體系結(jié)構(gòu)的并行編程模型——共享存儲編程模型、消息傳遞模型、異構(gòu)編程模型和混合編程模型進(jìn)行分析研究。
在共享存儲編程模型中,各個(gè)處理器可以對共享存儲器中的數(shù)據(jù)進(jìn)行存取,數(shù)據(jù)對每個(gè)處理器而言都是可訪問的,不需要在處理器之間進(jìn)行傳送,即數(shù)據(jù)通信是通過讀寫共享存儲單元來完成的。常見的共享內(nèi)存編程模型有:POSIX Threads、OpenMP[3]。
POSIX(Portable Operating System Interface of UNIX) Threads,簡稱為Pthreads,是一個(gè)可移植的多線程庫,它提供了在多個(gè)操作系統(tǒng)平臺上一致的程序設(shè)計(jì)接口。在該模型中,有一些能夠被單獨(dú)控制的并行執(zhí)行線程。Pthreads現(xiàn)已成為Linux操作系統(tǒng)中多線程接口的標(biāo)準(zhǔn),并已被UNIX平臺使用。Pthreads的主要功能集中在線程的生成、退出、互斥、同步以及一些輔助功能上。例如:
(1)線程的創(chuàng)建與退出 (create & exit)。任何進(jìn)程在啟動(dòng)時(shí)就已有了一個(gè)主線程,如果需要再生成線程則使用pthread_create函數(shù),在該函數(shù)中可以指定線程屬性、線程例程、傳給線程例程的參數(shù)。線程例程是線程執(zhí)行的代碼,是一個(gè)用戶自定義的函數(shù)。當(dāng)線程例程返回時(shí),線程則結(jié)束運(yùn)行,也可以通過調(diào)用pthread_exit來退出。
(2)線程間互斥(mutex)?;コ獠僮?,就是在對某段代碼或某個(gè)變量修改時(shí)只能有一個(gè)線程執(zhí)行這段代碼,而其他線程不能同時(shí)進(jìn)入這段代碼或同時(shí)修改變量.Pthreads常用pthread_mutex互斥體來實(shí)現(xiàn)線程互斥操作。
pthread_mutex_init函數(shù)用于初始化一個(gè)互斥體變量;pthread_mutex_lock函數(shù)用于給互斥體變量上鎖,如果上鎖時(shí)互斥體已經(jīng)被其他線程鎖住,那么調(diào)用該函數(shù)的線程將被阻塞,直到互斥體被解鎖為止;pthread_mutex_trylock函數(shù)的作用是試圖鎖住互斥體,但在互斥體已經(jīng)被加鎖時(shí)不會(huì)造成阻塞,而是迅速返回;pthread_mutex_unlock函數(shù)是對互斥體解鎖;pthread_mutex_destroy函數(shù)是用來釋放互斥體所占資源。
(3)線程同步(cond)。線程同步就是若干個(gè)線程等待某個(gè)事件的發(fā)生,當(dāng)該事件發(fā)生時(shí),這些線程同時(shí)執(zhí)行各自代碼。在Linux線程中用條件變量來實(shí)現(xiàn)同步。函數(shù)pthread_cond_init用來創(chuàng)建一個(gè)條件變量。
pthread_cond_wait和pthread_cond_timewait用來等待條件變量被設(shè)置,值得注意的是這兩個(gè)等待調(diào)用的函數(shù)需要一個(gè)已經(jīng)上鎖的互斥體mutex,這是為了防止在真正進(jìn)入等待狀態(tài)之前別的線程有可能設(shè)置該條件變量而產(chǎn)生競爭;pthread_cond_broadcast用于設(shè)置條件變量,即使事件發(fā)生,也使所有等待該事件的線程不再阻塞;pthread_cond_signal用于解除某一個(gè)等待線程的阻塞狀態(tài);pthread_cond_destroy用來釋放一個(gè)條件變量的資源[4]。
OpenMP(open multi-processing)編程模型是基于線程的并行編程模型,是一個(gè)共享存儲應(yīng)用編程接口(API)。OpenMP多線程接口被特別設(shè)計(jì),用來支持高性能并行計(jì)算程序,它包含許多編譯制導(dǎo)指令,具有移植性好和可擴(kuò)展等優(yōu)點(diǎn)。OpenMP是由指導(dǎo)性注釋、編譯指令以及線程池管理和庫例程結(jié)合在一起實(shí)現(xiàn)的,它與Pthreads不同,不是作為一個(gè)庫來實(shí)現(xiàn)的。這些指令指示編譯器創(chuàng)建線程、執(zhí)行同步操作和管理共享內(nèi)存等。
OpenMP中的常用函數(shù)有:
①void omp_set_num_threads(int num_threads):設(shè)置線程數(shù)目。通過該函數(shù)來指定其后用于并行計(jì)算的線程數(shù)目,其中參數(shù)num_threads就是指定的線程數(shù)目。
②int omp_get_num_threads():獲取線程數(shù)目。通過該函數(shù)可以獲取當(dāng)前運(yùn)行組中的線程數(shù)目,如果在并行結(jié)構(gòu)中使用該函數(shù),返回的就是現(xiàn)在并行計(jì)算中的所有的線程總數(shù);如果在串行中使用該函數(shù),其返回值就為1。
③int omp_get_max_threads():獲取最多線程數(shù)目。該函數(shù)將返回最多可以用于并行計(jì)算的線程數(shù)目。
④ int omp_get_num_procs():獲取程序可用的處理器數(shù)目。該函數(shù)將返回可用于程序的處理器數(shù)目(其實(shí)是線程數(shù)目)。
⑤ int omp_in_parallel():判斷線程是否處于并行狀態(tài)。該函數(shù)返回值為0時(shí)表示線程處于串行程序中,返回值為1時(shí)表示線程處于并行程序中。
消息傳遞模型是一種最常用的分布式存儲編程模型,它是通過處理器之間的信息交換來實(shí)現(xiàn)通信的,適用于分布式存儲系統(tǒng)。在該模型中,駐留在不同節(jié)點(diǎn)上的進(jìn)程可以通過網(wǎng)絡(luò)傳遞消息相互通信。它常用于開發(fā)大粒度和粗粒度的并行性。
MPI(Message Passing Interface)是一個(gè)消息傳遞接口的標(biāo)準(zhǔn),用于開發(fā)基于消息傳遞的并行程序,其目的是為用戶提供一個(gè)實(shí)際可用的、可移植的、高效和靈活的消息傳遞接口庫。因此,使用MPI,必須要和特定的語言如FORTRAN和C語言等結(jié)合起來[5]。
POSIX Pthreads、OpenMP和MPI等3種并行編程模型屬于共享存儲模型或分布式存儲模型。表1概括了3種編程模型在通常實(shí)際應(yīng)用中的實(shí)現(xiàn)特性[6]。
表1 3種并行編程模型實(shí)現(xiàn)特性
異構(gòu)并行編程模型主要是針對異構(gòu)計(jì)算機(jī)系統(tǒng)的并行編程。異構(gòu)計(jì)算機(jī)系統(tǒng)是由功能或性能相異的處理器通過一定的互聯(lián)結(jié)構(gòu)連接起來的計(jì)算系統(tǒng),一般由通用微處理器和專用加速器構(gòu)成。通常實(shí)際應(yīng)用較多的異構(gòu)并行編程模型有:CUDA、Opencl。
CUDA(Compute Unified Device Architecture,統(tǒng)一計(jì)算設(shè)備架構(gòu))是NVIDIA公司開發(fā)的一種并行編程模型,它是一種將GPU作為數(shù)據(jù)并行計(jì)算設(shè)備的軟硬件體系。此外,CUDA提供了一個(gè)允許開發(fā)者使用C語言或者更高級語言的軟件環(huán)境。對于CUDA來說,一個(gè)并行系統(tǒng)包含一個(gè)主機(jī)(Host)和計(jì)算資源或者設(shè)備(device)。CUDA編程模型通常將CPU作為主機(jī),GPU作為設(shè)備或者協(xié)處理器(co-processor)。計(jì)算任務(wù)是在GPU中依靠一組并行執(zhí)行的線程來完成的。CUDA將計(jì)算任務(wù)映射為大量的可以并行執(zhí)行的線程,通過硬件動(dòng)態(tài)調(diào)度來執(zhí)行這些線程。
CUDA的線程結(jié)構(gòu)包含2個(gè)層次結(jié)構(gòu):Grid(線程網(wǎng)格)和Block(線程塊)。由圖1所示的CUDA存儲結(jié)構(gòu)可以看出,這2個(gè)層次內(nèi)也存在并行,即Grid中的Block間并行和Block中的Thread間并行。
圖1 CUDA存儲結(jié)構(gòu)
CUDA的體系結(jié)構(gòu)是以Grid的形式組織的,每個(gè)Grid由若干個(gè)Block組成,而每個(gè)Block又由若干個(gè)Thread組成,它們都擁有自己的ID,用以和其他線程相區(qū)分。在圖1的底部,Global Memory(全局存儲器)和Constant Memory(常數(shù)存儲器)能夠被主機(jī)代碼寫入和讀取。常數(shù)存儲器允許設(shè)備只讀訪問,在Block里,可以有共享存儲器、寄存器或本地存儲器,共享存儲器能夠在Block里被所有的線程訪問,而寄存器則對每一個(gè)線程都是獨(dú)立的。同一個(gè)Block中的線程通過共享存儲器交換數(shù)據(jù),并通過柵欄同步保證線程間能夠正確地共享數(shù)據(jù),從而實(shí)現(xiàn)Block內(nèi)通信。
通常情況下,一個(gè)完整的CUDA程序包括在Host上執(zhí)行的串行代碼以及在GPU上并行執(zhí)行的程序(kernel函數(shù))。在圖1中,每進(jìn)行一次GPU計(jì)算,需要在多種存儲器之間進(jìn)行數(shù)據(jù)傳輸,這會(huì)消耗大量的時(shí)間,造成延遲,因此,單獨(dú)的CUDA并不適合于一些對實(shí)時(shí)性要求很高的應(yīng)用,往往還需要與CPU搭配協(xié)同運(yùn)行[7]。
OpenCL(Open Computing Language,開放計(jì)算語言)是一個(gè)在由CPU、GPU和其他類型處理器組成的異構(gòu)平臺上進(jìn)行通用并行編程的免費(fèi)的標(biāo)準(zhǔn)。它是由用于編寫kernels(在OpenCL設(shè)備上運(yùn)行的函數(shù))的基于C99的語言和一組用于定義并控制平臺的API組成。
OpenCL操作規(guī)范模型可描述成4個(gè)相關(guān)的模型:平臺模型、執(zhí)行模型、內(nèi)存模型、編程模型。
平臺模型:描述了協(xié)同執(zhí)行的單個(gè)處理器及能執(zhí)行OpenCL代碼的處理器,定義了一個(gè)抽象的硬件模型,讓開發(fā)者能夠編寫在這些設(shè)備上執(zhí)行的kernel。
執(zhí)行模型:定義了如何在主機(jī)上配置OpenCL環(huán)境,以及如何在設(shè)備上執(zhí)行kernel。
內(nèi)存模型:定義了被kernel所用的抽象內(nèi)存層次。
編程模型:定義了如何將并發(fā)模型映射到物理硬件上。
上述4個(gè)模型,提供了基于任務(wù)和基于數(shù)據(jù)的2種并行計(jì)算機(jī)制,極大地?cái)U(kuò)展了GPU的應(yīng)用范圍,而且由于它是跨平臺的基于異構(gòu)的編程模型,在今后很長一段時(shí)期,還將會(huì)持續(xù)得到發(fā)展[8]。
混合編程模型是將共享存儲和分布式存儲編程相結(jié)合的一種方法,它在節(jié)點(diǎn)之間使用消息傳遞來發(fā)送和接受數(shù)據(jù),在節(jié)點(diǎn)內(nèi)通過共享內(nèi)存來進(jìn)行數(shù)據(jù)運(yùn)算,充分利用了兩種編程模型的優(yōu)點(diǎn)[6]。這種混合編程模型也非常符合當(dāng)今混合硬件體系結(jié)構(gòu)的發(fā)展趨勢。實(shí)際應(yīng)用中出現(xiàn)的主要混合并行編程模型有:MPI+OpenMP,CUDA+OpenMP,CUDA+MPI等。
MPI可以解決多處理機(jī)間的粗粒度通信,而OpenMP提供的輕量級線程可以更好地解決每個(gè)多處理器計(jì)算機(jī)內(nèi)部各處理器間的交互,通常在用MPI實(shí)現(xiàn)的原始程序中加入OpenMP編譯制導(dǎo)語句,就能使程序轉(zhuǎn)化為MPI和OpenMP混合編程模式的程序,原理如圖2所示。
圖2 MPI+OpenMP混合編程模型
下面給出簡單的細(xì)粒度混合編程模型代碼:
......
MPI_INIT_THREAD();//初始化進(jìn)程,使其具有多線程功能
MPI_COMM_RANK();
MPI_COMM_SIZE();
.....MPI communiaciton and some computation
#pragma omp parallel//調(diào)用OpenMP
#pragma omp for//多個(gè)線程并行地執(zhí)行for循環(huán)的代碼
for(...)
{.......computation
}
......computation and MPI communication
MPI_FINALIZE();
3.1.3 進(jìn)樣精密度與重復(fù)性 取“2.4.5”項(xiàng)下中間濃度對照品溶液,按“2.1”項(xiàng)下方法進(jìn)行檢測,連續(xù)進(jìn)樣6次,硫酸鹽峰面積的RSD為0.5%(n=6),表明儀器精密度良好。精密稱取注射用硫酸核糖霉素樣品,共6份,按“2.4.2”項(xiàng)下方法配制供試品溶液,同法檢測,6份樣品硫酸鹽含量的RSD為0.9%(n=6),表明重復(fù)性良好。
該混合模型是在CUDA模型的基礎(chǔ)上,通過在執(zhí)行主程序時(shí)加入OpenMP編譯制導(dǎo)語句,在CPU端產(chǎn)生大量的線程,這些線程一方面控制GPU,調(diào)動(dòng)kernel函數(shù)并行執(zhí)行分配給GPU的計(jì)算任務(wù),另一方面也能夠并行化處理在主機(jī)上的串行程序,因此在整體上提高了程序的執(zhí)行效率。其不足之處是CPU和GPU之間的數(shù)據(jù)傳輸將會(huì)影響到GPU的計(jì)算效率。這種模型通常適合多GPU的系統(tǒng)。該模型的簡單代碼框架如下:
//主機(jī)端程序
#include
#include
#include
_global_[......]//CUDA內(nèi)核程序kernel
......
Main Program
cudaGetDeviceCount(&num_gpus);//獲得系統(tǒng)中支持CUDA的GPU數(shù)量
//顯示GPU與CPU的信息
printf("number of host CPUs: %d ",omp_get_num_procs());
printf("number of CUDA devices: %d ",num_gpus);
......
#pragma omp parallel
...... //并行執(zhí)行kernel
......
parallel_execute_host_code//執(zhí)行host上的串行代碼
......
...correctsult()//檢查對比CPU和GPU的結(jié)果
這種模型也是一種基于CPU和GPU的異構(gòu)模型,充分利用了MPI在節(jié)點(diǎn)間進(jìn)行消息傳遞和在節(jié)點(diǎn)內(nèi)進(jìn)行大量線程計(jì)算的特點(diǎn),非常適合實(shí)現(xiàn)集群或者超級計(jì)算機(jī)中的多節(jié)點(diǎn)多GPU并行計(jì)算。在這個(gè)模型中,MPI用于控制程序、節(jié)點(diǎn)間通信和數(shù)據(jù)調(diào)度以及CPU之間的交互,而CUDA負(fù)責(zé)在GPU中的計(jì)算任務(wù)。
下面為該混合模型的部分關(guān)鍵代碼:
/*聲明對MPI以及標(biāo)準(zhǔn)庫的引用*/
#include"mpi.h"
#include
......
//啟動(dòng)CUDA函數(shù)
#if_DEVICE_EMULATION_
Bool InitCUDA(int myid){return true;}
#else
.......
//獲得CPU所在節(jié)點(diǎn)中的CUDA設(shè)備數(shù)量,沒有則返回
cudaGetDeviceCount(&count);
if(count==0){
fprintf(stderr,"There is no device. ");
return false;
}
......
_global_[......]//設(shè)備端kernel函數(shù)的定義和調(diào)用
......
//啟動(dòng)MPI環(huán)境
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
......
execute_kernel();//執(zhí)行內(nèi)核函數(shù)
......
MPI_Finalize();
}
從上面幾種模型中不難發(fā)現(xiàn),沒有一種混合編程模型完全適用于當(dāng)今所有的計(jì)算機(jī)體系結(jié)構(gòu)。因此,只能選擇那些最適合于硬件宿主特性的編程模型,才能最大程度地提高并行程序的性能。
隨著未來計(jì)算機(jī)微處理器朝著眾核處理器方向的發(fā)展,以及大規(guī)模機(jī)群的不斷出現(xiàn),原來單一的編程模型已很難適應(yīng)這種新的體系結(jié)構(gòu),因此,基于異構(gòu)平臺的混合并行編程在今后的大規(guī)模并行應(yīng)用中必將成為主流。
參考文獻(xiàn):
[1] Feng Wu-chun , Balaji Pavan.Tools and Environment for Multicore and Many-core Architectures[J].IEEE Computer, 2009, 42(12): 26-27.
[2] 范平.芯革命新未來 英特爾開啟MIC時(shí)代 [EB/OL]. (2013-06-08).http://server.zol.com.cn/246/2468105_all.html.
[3] 陳明. 反投影算法在雙基地合成孔徑雷達(dá)成像中的應(yīng)用[D].北京:中國科學(xué)院研究生院,2007.
[4] 劉明剛.基于嵌入式Linux的開放式數(shù)控系統(tǒng)研究與實(shí)現(xiàn)[D].成都:電子科技大學(xué),2005.
[5] 張玉斌. 迭代動(dòng)態(tài)規(guī)劃算法及并行化研究 [D].青島:中國石油大學(xué),2008.
[6] Javier Diaz,Camelia Munoz Caro,Alfonso Nino.A Survey of Parallel Programming Models and Tools in the Multi and Many-core Era.[J].IEEE Transactions on Parallel and Distributed Systems, 2012, 23(8): 1368-1386.
[7] 張舒,褚艷利.GPU高性能運(yùn)算之CUDA[M].北京:中國水利水電出版社,2009:13-44.
[8] Benedict R Gaster,Lee Howes,David R Kaeli,et al.OpenCL異構(gòu)計(jì)算[M].張?jiān)迫?,張先軼,等譯.北京:清華大學(xué)出版社, 2012:15-26.