桑 喆,鄧 川,茍 聰,劉開興,白明澤
(1.重慶郵電大學(xué)生物信息學(xué)院,重慶 400065; 2.重慶郵電大學(xué)軟件工程學(xué)院,重慶 400065)
在最新頒布的2016年全球超算TOP500的榜單[1]中,有18%的超算采用異構(gòu)系統(tǒng),利用GPU等加速卡強大的浮點計算能力來提升系統(tǒng)的計算性能。而這一比值在2013年還只是10.8%,3年間增長了67%,可以預(yù)見在今后會有越來越多的超算將會采用異構(gòu)系統(tǒng)。我國自主研發(fā)的超級計算機——天河2號[2],曾連續(xù)6年位列全球超算TOP500榜單的榜首,采用的是Intel Xeon Ivy Bridge CPU芯片和Intel Xeon Phi協(xié)處理器混合的異構(gòu)眾核并行架構(gòu)。Intel Xeon Phi協(xié)處理器是首款英特爾集成眾核(Many Integrated Core, MIC)架構(gòu)產(chǎn)品,擁有比常規(guī)GPU更多的核心,并且是基于x86處理器架構(gòu)。與CPU相同的架構(gòu)則意味著它對已有的程序有較好的兼容性。
MIC支持C/C++/Fortran這3種編程語言,由于MIC是基于x86處理器架構(gòu),這使得MIC在以Native模式工作時,可以作為獨立計算節(jié)點直接運行既有的C/C++/Fortran程序代碼;而在以O(shè)ffload模式工作時,可以作為協(xié)處理器輔助CPU計算。通過對需要在MIC上運行的代碼塊添加Offload引語,MIC即可隱式地幫助完成數(shù)據(jù)的拷貝操作。但普通的GPU編程則需要程序員重新編寫代碼并手動完成數(shù)據(jù)的拷貝。MIC這2種模式的靈活性及其易編程性可以在一定程度上減輕程序員的負擔(dān)。盡管MIC上可以安裝定制的Linux系統(tǒng)并作為獨立節(jié)點進行計算,但當(dāng)前Intel并未在MIC上提供Java環(huán)境的支持。這帶來一個問題就是MIC的協(xié)處理器難以將自己強大的計算能力發(fā)揮到大量用Java語言開發(fā)的許多大數(shù)據(jù)應(yīng)用上。隨著Hadoop[3],Spark[4]等大數(shù)據(jù)處理并行計算框架的廣泛應(yīng)用,很多大數(shù)據(jù)處理的應(yīng)用都是采用Java編寫的。常被用來作為加速卡的GPU在這個方面的發(fā)展則成熟得多,有較多的方法能夠?qū)崿F(xiàn)Hadoop+GPU的聯(lián)合使用[5]:1)通過JNI[6],JNA[7]接口調(diào)用C++的Native代碼供Java程序使用;2)Java Aparapi[8]能夠?qū)ava字節(jié)碼轉(zhuǎn)換為OpenCL并在GPU上執(zhí)行;3)Nvidia CUDA提供有Java綁定和相關(guān)的庫JCUDA[9]。
為了解決Java程序難以利用MIC計算資源的問題,本文研究JNI(Java Native Interface)技術(shù)與C++中的OpenMP[10]并行技術(shù),提出基于JNI和C++的MIC混合并行方法,并對MIC并行的Java程序和MIC串行的Java程序的性能進行比較。本文主要的工作為以下2方面:
1)提出在Intel MIC協(xié)處理器上適配Java程序的并行方法。
2)評估、分析MIC在進行向量運算時的加速比。
評估結(jié)果表明在開啟不同的線程數(shù)進行計算以及在不同的計算數(shù)據(jù)量下,MIC的計算效率有著不同的表現(xiàn)。隨著計算數(shù)據(jù)量的增長,將MIC協(xié)處理器的線程全部開啟用于計算,相對于MIC上的單線程計算所獲得的加速比是比較好的。
集成眾核(MIC)[11]是英特爾設(shè)計的專用于高性能計算的協(xié)處理器架構(gòu),其產(chǎn)品是Intel Xeon Phi芯片。本文中在討論架構(gòu)的時候用MIC來指代,在討論具體硬件指標(biāo)的時候用Xeon Phi指代。與常規(guī)CPU相比,Xeon Phi有著許多新的特性。它包含了57個簡化了的x86核心,每一個核心通過特殊的超線程技術(shù)可以并發(fā)執(zhí)行4個線程,并且擁有一個強大的向量處理單元(VPU),其向量寬度可以支持512 bits的向量計算。這對于高性能計算中需要大量單指令多數(shù)據(jù)運算的程序而言是極為重要的。
Xeon Phi的內(nèi)存容量相對CPU的內(nèi)存較小,這對某些算法而言可能是個性能瓶頸。每一個MIC核心擁有32 kB的一級數(shù)據(jù)緩存、32 kB的一級指令緩存和512 kB的二級緩存。圖1給出了以緩存共享為主要特點的MIC協(xié)處理器架構(gòu),它通過一個高速環(huán)形拓撲總線連接MIC內(nèi)核的同時也連接了所有L2高速緩存。有數(shù)據(jù)訪問需求時,內(nèi)核間首先訪問彼此的L2高速緩存而不是訪問內(nèi)存,從而大大提升了數(shù)據(jù)通信效率。因為眾核的架構(gòu)中并沒有L3高速緩存,數(shù)據(jù)從L2高速緩存出來之后,讀取速度將大幅降低。但是,Xeon Phi仍然有著高達350 GB/s的內(nèi)存帶寬和5 GB/s的總線速度。此外,Xeon Phi協(xié)處理器與CPU通過PCIe線纜連接,但主協(xié)處理器的內(nèi)存空間相互獨立,并未共享。
圖1 MIC協(xié)處理器架構(gòu)
MIC協(xié)處理器主要支持2種編程模型[12]:Offload模式和Native模式。在Native模式下,MIC可被當(dāng)作是一個獨立的SMP系統(tǒng),可安裝定制的Linux系統(tǒng),并支持TCP/IP通信和眾多標(biāo)準(zhǔn)API,如MPI[13]。在MIC上以Native模式運行程序之前,用戶需要將程序代碼和所需的數(shù)據(jù)從CPU節(jié)點傳輸?shù)組IC節(jié)點上,并通過SSH登錄到MIC卡上才能運行程序。由于MIC內(nèi)存容量的限制,內(nèi)存需求大的程序并不適合在MIC上運行。以Native模式運行的程序的編譯和運行比較簡單:在使用ICC(Intel C++ Compiler)編譯C/C++代碼時,加上-mmic參數(shù),即得到可以在MIC上運行的可執(zhí)行文件。
在Offload模式下,MIC以協(xié)處理器的身份輔助CPU工作,完成CPU分配的子任務(wù)。在此模式下,程序代碼和計算所需的數(shù)據(jù)可以動態(tài)地由CPU傳輸至MIC,不需要一次性全部加載,所以MIC上的內(nèi)存容量在Offload模式下一般不會成為一個大的性能瓶頸。對于在MIC上執(zhí)行的代碼,可用#pragma offload的指令語句在行首進行標(biāo)注。
無論是以Native模式還是Offload模式運行,都需要諸如OpenMP,TBB[14],Cilk[15]和OpenCL[16]等多線程并行編程模型來幫助實現(xiàn)程序在MIC上的并行化。本文選用Offload模式,因為使用Offload模式可以避免消耗太多的內(nèi)存空間。此外,MIC可以只運行程序中計算量高度集中部分的代碼,發(fā)揮其計算性能;而CPU則負責(zé)執(zhí)行較為復(fù)雜的邏輯,從而避免對原有Java代碼做過多的更改。
盡管MIC是HPC領(lǐng)域的一顆新星,它已經(jīng)被許多科研、工業(yè)團隊廣泛使用。天河2號現(xiàn)在已經(jīng)被用于天氣預(yù)測、粒子撞擊模擬、汽車碰撞模擬等,這表明大量科學(xué)程序已經(jīng)使用MIC加速。而這些應(yīng)用實例成功應(yīng)用也是源于一些重要的科學(xué)算法的成功移植,比如矩陣相乘、快速傅氏變換、分子動力學(xué)等。
OpenMP(Open Multi-Processing)是一個在C/C++/Fortran中支持多平臺共享內(nèi)存并行編程接口。OpenMP采用Fork-join模式:在程序剛開始運行的時候只有一個主線程運行,當(dāng)執(zhí)行到并行域時,由主線程派生出若干個子線程(Fork),然后系統(tǒng)將并行域的計算任務(wù)劃分并分配給各線程進行并行計算,并行域子任務(wù)全部完成之后,各線程可按照代碼指示將一些數(shù)據(jù)歸攏(Join)到主線程。在并行域的任務(wù)執(zhí)行結(jié)束之前,程序不會執(zhí)行串行部分代碼。使用OpenMP可以使程序員較為輕松地編寫可移植的多線程程序,不需要手動進行復(fù)雜的線程創(chuàng)建、同步、負載均衡和銷毀等工作。
因此,當(dāng)需要將程序在MIC上并行化時,采用OpenMP是種較為容易的方法。
Java Native Interface (JNI)是一個編程框架,它使運行在Java虛擬機中的Java代碼能夠調(diào)用Native層的程序或者是C/C++編寫的庫文件;也可以使Java代碼被Native層所調(diào)用。有時候一個應(yīng)用程序不能完全用Java語言來編寫,或是出于效率需要使用C/C++編寫的執(zhí)行效率更高的程序;或是出于硬件原因需要采用更底層的C/C++語言才可以控制硬件。此時,JNI就可以作為Java代碼和C/C++代碼之間的橋梁,使Java和C/C++混合編程得以實現(xiàn)。
與JNI相關(guān)的還有JNA(Java Native Acess)技術(shù),它是建立在JNI之上的Java開源框架,所以其效率不如JNI,但開發(fā)人員可以更簡單快捷地編寫Native層的代碼?;谛实目紤],本文選用JNI技術(shù)來探索利用MIC計算資源加速Java程序的可行性。
本文根據(jù)MIC的體系結(jié)構(gòu)/編程環(huán)境和JNI技術(shù)提出支持Java并行的混合模型,如圖2所示(圖中實線代表程序的調(diào)用和數(shù)據(jù)傳入,虛線代表被調(diào)用程序返回的數(shù)據(jù))。在這個模型中,Java主程序通過JNI調(diào)用Native層的C++程序,實現(xiàn)2種語言的混合計算;Native層的C++程序通過OpenMP的幫助,將需要高性能計算的代碼通過Offload模式提交到MIC的多核上并行計算,并接收來自MIC的計算數(shù)據(jù)。JNI也是Java代碼到C++代碼的數(shù)據(jù)傳輸接口,運行在CPU上的Native層C++代碼會從JNI接收來自Java程序的數(shù)據(jù);之后它會對其進行再度處理,使其符合MIC協(xié)處理器數(shù)據(jù)傳輸?shù)囊螅⑼ㄟ^Offload語句傳輸?shù)組IC上開啟多線程進行并行計算。計算的結(jié)果會被返回給CPU中運行的Native層C++程序,在被其處理、包裝之后通過JNI接口傳輸?shù)絁ava程序中?;旌夏P蛯⒂嬎阌蒍ava代碼遷移到C++代碼、由CPU遷移到MIC上,從而實現(xiàn)CPU-MIC的主副協(xié)同計算。
圖2 混合并行方法架構(gòu)
混合模型的指導(dǎo)思想是,程序中較為復(fù)雜的邏輯仍由CPU執(zhí)行,保持原有Java代碼不變;而那些適合并行化的、計算量較大的計算任務(wù)則通過JNI接口傳輸?shù)紺++Native層,由Native層執(zhí)行。
方法具體流程為:首先要對原有Java代碼進行分析,分離出需要并行化的部分,設(shè)計其C/C++并行方案;然后,根據(jù)C/C++方案所需要的數(shù)據(jù)來設(shè)計JNI接口,需要確保數(shù)據(jù)的正確傳輸;最后完成C++的并行程序的編碼,這里需要用到OpenMP。
圖3給出了一個矩陣相乘的Java/C++混合并行化實現(xiàn)過程,將其作為例子說明本文提出的混合并行方法的具體實現(xiàn)過程。
1)在Java項目中創(chuàng)建一個聲明了Native方法的類,比如MICArrayMultipy.java,其中包含著一個public native int[] compute(int[] array_1, int[] array_2, int size)的方法,native是關(guān)鍵詞,表明該方法是Native層的程序,將由C++語言實現(xiàn)。Java程序調(diào)用該方法時,將傳入2個整型數(shù)組及數(shù)組的大小,計算結(jié)束后返回一個數(shù)組。
2)使用javac編譯該Java代碼,獲得class文件。
3)使用javah-jni命令和Java類名生成C++的頭文件,這個頭文件中包含著Native方法的聲明。
4)在與頭文件同名的cpp文件中編寫C/C++代碼實現(xiàn)Native方法。為實現(xiàn)MIC并行,在計算相乘的for循環(huán)加上Offload語句,意在將這個for循環(huán)放在MIC上并行計算。同時還要在Offload代碼塊上加上OpenMP語句,將這段for循環(huán)開辟為并行域,選擇適當(dāng)?shù)恼{(diào)度策略和線程數(shù)量。
5)將Native層的代碼編譯為一個動態(tài)鏈接庫,在Java代碼中使用System.loadLibrary()載入動態(tài)庫。
6)運行程序。
圖3 混合并行方法的實現(xiàn)步驟
本文設(shè)計了一組實驗,以檢驗基于JNI和MIC的Java/C++混合編程模型的性能。實驗平臺為部署在國家蛋白質(zhì)科學(xué)中心(北京)的與天河2號同結(jié)構(gòu)的高性能計算平臺中的一個計算節(jié)點,它配備一個Ivy Bridge Xeon處理器(3.07 GHz)與一個Xeon Phi協(xié)處理器,CPU擁有64 GB的內(nèi)存,MIC擁有8 GB的內(nèi)存。操作系統(tǒng)是國產(chǎn)麒麟操作系統(tǒng)(版本NeoKylin release 3.2,內(nèi)核版本2.6.32-431.29.2.2.ky3.1.x86_64)。Java的版本為1.8,使用ICC編譯器(版本14.0.2)將Native代碼編譯為一個動態(tài)鏈接庫,在Java代碼中載入并調(diào)用該動態(tài)庫。
在實驗方法的選擇上,本文沒有使用Linpack[17]這樣的Benchmark程序來做性能測試,而是根據(jù)MIC協(xié)處理器擅長進行向量運算,選擇向量乘法計算進行實驗。因為Linpack是通過計算N階的線性代數(shù)方程組的時間來估測計算機每秒的浮點運算,它一般被用來評測不同硬件平臺的浮點計算能力。本文要比較的是不同的編程模型的性能差異。
做向量乘法的數(shù)組大小分別為1000,10000,100000,1000000這4個等級,旨在通過設(shè)定數(shù)量級遞增的4個測試數(shù)據(jù),測試MIC在進行較大數(shù)據(jù)量的計算任務(wù)時的表現(xiàn)。本文測試了MIC上的串行和不同線程數(shù)程序的并行計算時間,線程數(shù)分別為:32,64,128,256(超出MIC核數(shù)量的線程數(shù)用來測試Intel的超線程技術(shù)[18])。向量乘是一種非常簡單的運算,為了加長計算時間以盡量減小其它程序的干擾,在每次測試中都讓乘法重復(fù)計算1000次。測試的計算時間結(jié)果如表1所示,表中的時間值為重復(fù)5次測試后取得的中位數(shù)值。
表1 單、多線程下運行時間/ms
線程數(shù)數(shù)組大小10001000010000010000001270.4374.544834224432325.2374.6548.22529.464344.2370503.81489.4128405.6429.74721052256520.4543.65711032.8
為更好地比較模型的并行性能,本文計算并行計算的加速比。加速比公式:Sp=Tp/T1,其中Sp為加速比,Tp是p條線程程序的運行時間,T1是單核/單線程下的運行時間。加速比結(jié)果如表2所示。為更直觀觀察加速比結(jié)果,圖4給出了以數(shù)組大小為視角的加速比折線圖,4條折線分別代表著4種數(shù)組大小(小于1.2的值用實線表示,大于1.2的值用虛線表示)。從圖4可以明顯地觀察到,在數(shù)組較小(1000和10000)時,加速比均小于或等于1,而且隨著線程數(shù)的增多而下降。這說明在數(shù)據(jù)量較小時,并行計算根本沒有得到性能提升。當(dāng)數(shù)組大小增長到100000時,可以發(fā)現(xiàn)加速比在線程數(shù)為32~128時有平穩(wěn)的提升,但是在線程數(shù)進一步增長到256時開始下降。而數(shù)組大小為1000000時,隨著線程數(shù)的增加,加速比有大幅提升,從16.7快速增長到40.16(線程數(shù)為128)。在線程數(shù)為256時,出現(xiàn)了與100000的情況類似性能下降的情況。
表2 不同數(shù)組大小下各線程數(shù)取得的加速比
線程數(shù)數(shù)組大小1000100001000001000000320.831.008.1816.70640.791.018.9028.361280.670.879.5040.162560.520.697.8540.90
圖4 以數(shù)組大小為主視角的加速比折線
圖5從線程數(shù)量角度展示了各種實驗組合的加速比。通過圖5可以看出整體的趨勢是,多線程加速的效果隨著數(shù)組規(guī)模的增大而提高。而且在數(shù)組大小為100000時,大于32的線程數(shù)沒有獲得更好的并行性能。在數(shù)組大小達到1000000的時候,加速比差異才體現(xiàn)得較明顯:線程數(shù)越多,加速越顯著(線程數(shù)為256時除外,后面將討論)。
圖5 以線程數(shù)量為主視角的加速比折線圖
下面討論這些性能差異產(chǎn)生的原因。在數(shù)組大小為1000~10000時,采用MIC進行多線程并行計算耗時(成本)要多于多線程并行所節(jié)約的時間(收益),因此并行反而比串行更慢。這里的成本包括:CPU-MIC之間的數(shù)據(jù)傳輸、OpenMP的初始化、并行域的創(chuàng)建、數(shù)據(jù)的分發(fā)和歸攏等一系列行為所需的時間。這里面比重較大的是CPU與MIC協(xié)處理器之間的通信開銷,它把大量的數(shù)據(jù)在CPU和MIC協(xié)處理器之間傳遞,耗費了較多的時間成本。
當(dāng)數(shù)組大小增長到100000和1000000時,并行計算都得到了明顯的加速,這是因為在成本增長較少的時候,并行計算的收益快速增長。另外,在這2個數(shù)據(jù)量級的條件下,線程數(shù)128并行性能最好。這與英特爾超線程技術(shù)是有關(guān)的,通過虛擬邏輯核心,得到2倍于原處理器核心數(shù)的線程,而MIC協(xié)處理器有57個計算核心,乘以2得到接近128的線程數(shù)量。當(dāng)線程數(shù)量繼續(xù)增長到256時,由于這超出了系統(tǒng)所能提供的量,只會在沒有更多收益的情況下增加并行操作成本,從而減慢總的計算速度。
綜上,選擇較大的數(shù)據(jù)規(guī)模和合適的線程數(shù)(核心數(shù)×2),本文提出的JNI/C++混合并行方法可以取得良好的并行加速成績。
Java語言編寫的程序無法直接采用MIC協(xié)處理器進行并行化,這影響了這些應(yīng)用在基于MIC的高性能計算機上的應(yīng)用推廣,也不利于計算機資源的有效使用。本文因此提出JNI與C++的混合并行方法。將JNI作為溝通Java主程序和C++計算密集代碼的接口,使數(shù)據(jù)可以由Java程序載入C++程序中,進而調(diào)用強大的MIC協(xié)處理器進行計算。分析與測試表明,MIC協(xié)處理器的計算資源可以通過該并行方法被Java程序調(diào)用,并在數(shù)據(jù)量較大和線程數(shù)設(shè)置合適時獲得較好的性能提升。這表明在涉及大量的復(fù)雜計算時,可以利用MIC強大的并行計算能力給程序性能帶來提升,甚至是用Java語言編寫的程序也可以做到這一點。
未來將繼續(xù)深入研究數(shù)據(jù)在MIC上的持久性問題,即如何在Offload模式下進行迭代計算時,減少數(shù)據(jù)向MIC和CPU之間的拷貝次數(shù),降低通信成本、提高效率。
參考文獻:
[1] 國際TOP500組織. 全球超級計算機排行榜TOP500[DB/OL]. https://www.top500.org/lists/2017/06/, 2017-06-30.
[2] 王濤. “天河二號”超級計算機[J]. 科學(xué), 2013,65(4):52.
[3] White T. Hadoop: The Definitive Guide[M]. Yahoo Press, 2011.
[4] Zaharia M, Chowdhury M, Franklin M J, et al. Spark: Cluster computing with working sets[C]// Proceedings of the 2nd USENIX Conference on Hot Topics in Cloud Computing. 2010: Article No. 10.
[5] Zhu Jie, Li Juanjuan, Hardesty E, et al. GPU-in-Hadoop: Enabling MapReduce across distributed heterogeneous platforms[C]// Proceedings of the 13th IEEE/ACIS International Conference on Computer and Information Science. 2014:321-326.
[6] 任俊偉,林東岱. JNI技術(shù)實現(xiàn)跨平臺開發(fā)的研究[J]. 計算機應(yīng)用研究, 2005,22(7):180-184.
[7] Fast T, Wall T, Chen Liang. Java Native Access (JNA)[EB/OL]. https://github.com/twall/jna, 2007-05-09.
[8] Okur S, Radoi C, Lin Yu. Hadoop+Aparapi: Making Heterogenous MapReduce Programming Easier[DB/OL]. http://www.semihokur.com/docs/okur2012-hadoop_aparapi.pdf, 2012-04-10.
[9] Yan Yonghong, Grossman M, Sarkar V. JCUDA: A programmer-friendly interface for accelerating Java programs with CUDA[C]// Proceedings of the 15th International Euro-Par Conference on Parallel Processing. 2009:887-899.
[10] Chandra R, Dagum L, Kohr D, et al. Parallel Programming in OpenMP[M]. Morgan Kaufmann, 2001.
[11] Chrysos G. Intel?Xeon PhiTMcoprocessor (codename knights corner)[C]// Proceedings of the 24th Hot Chips Symposium. 2012, doi: 10.1109/HOTCHIPS.2012.7476487.
[12] Wang Endong, Zhang Qing, Shen Bo, et al. High-performance Computing on the Intel?Xeon PhiTM: How to Fully Exploit MIC Architectures[M]. Springer, 2014.
[13] Clarke L, Glendinning I, Hempel R. The MPI message passing interface standard[M]// Programming Environments for Massively Parallel Distributed Systems. Birkhauser, 1994:213-218.
[14] Reinders J. Intel Threading Building Blocks: Outfitting C++ for Multi-core Processor Parallelism[M]. O’Reilly Media, 2007.
[15] Randall K. Cilk: Efficient Multithreaded Computing[R]. Massachusetts Institute of Technology, 1998.
[16] Stone J E, Gohara D, Shi Guochun. OpenCL: A parallel programming standard for heterogeneous computing systems[J]. Computing in Science & Engineering, 2010,12(3):66-73.
[17] Dongarra J J. The Linpack benchmark: An explanation[C]// Proceedings of the 1st International Conference on Supercomputing. 1987:456-474.
[18] Tian Xinmin, Chen Yenkuang, Girkar M, et al. Exploring the use of hyper-threading technology for multimedia applications with Intel?OpenMP*compiler[C]// Proceedings of the 17th International Symposium on Parallel and Distributed Processing. 2003, doi:10.1109/IPDPS.2003.1213118.