宋強,唐俊龍,陳照云,時洋,譚期軒,肖紫陽,鄒望輝
(1.長沙理工大學物理與電子科學學院,湖南 長沙 410114; 2.國防科技大學計算機學院,湖南 長沙 410073)
隨著人工智能、大數據、云服務等新興技術的發展,計算已經成為科學研究與工業生產必不可少的工具與手段[1]。高性能計算在材料科學[2]、大數據[3-4]、地震模擬[5]、天文觀測、生物醫學等領域的大規模數據模擬與仿真中發揮著重要的作用,是推動國家科技發展與進步的有力手段之一。國防科技大學作為國內最早開始高性能計算處理器自主設計的優勢單位之一,研發了采用中央處理器(CPU)+通用數字信號處理器(GPDSP)片上異構融合架構的高性能加速器,其中GPDSP加速核采用超長指令集(VLIW)+單指令多數據流(SIMD)的向量化架構,具備高性能、低功耗的特點,是新一代超算系統核心計算芯片的有力競爭者之一。為了充分發揮GPDSP的硬件性能,提升用戶編程友好性,從編譯器層面實現對GPDSP的良好支持與并行性挖掘是可行的辦法之一,也是當下亟待解決的重要問題。
低級虛擬器(LLVM)是當前主流的編譯框架之一,統一的中間表達(IR)與高度模塊化的設計是LLVM所具備的獨特優勢。高度模塊化的設計降低了開發者工作的難度,統一的IR加強了對高級編程語言的支持,豐富的工具集極大地降低了開發者的工作量[6]。然而現有LLVM編譯框架對國產高性能加速器兼容效果較差,無法充分發揮GPDSP加速核的硬件優勢與結構優勢,主要體現在以下幾個方面:1)指令調度模塊是面向通用處理器設計的,對VLIW體系結構的支持不夠完善,在靜態功能單元分配以及完整的指令打包與排布優化上具備可提升的空間;2)密集的數據計算指令排布容易造成更多的寄存器溢出,極大程度上影響性能發揮,尤其是對于靜態指令排布的VLIW架構;3)不支持GPDSP特有的向量指令,增加了開發者的開發難度,無法充分發揮GPDSP結構在數據級并行上的優勢。
本文基于主流LLVM編譯框架,結合高性能加速器GPDSP的體系結構特點,優化功能單元分配沖突檢測機制,提出支持靜態功能單元分配與寄存器壓力感知[7]的指令調度策略,充分挖掘程序指令級并行性能,為基于LLVM編譯框架的VLIW指令調度研究與實現提供借鑒;在此基礎上,結合自主GPDSP指令集,為用戶提供一系列豐富且規整的向量指令接口,從而有效降低用戶并行開發的難度,并且為用戶程序在指令集層面提供更多數據并行優化的可能;最后,結合高性能加速器當前研究熱點與發展趨勢,對未來編譯器優化方向提出一些后續的思考和討論。
高性能加速器核GPDSP基于自研指令集設計,采用VLIW+SIMD的標向量融合架構,最大可支持11條指令發射以及16寬度的向量單元[8],其內核部分的主要結構如圖1所示。該加速器主要由包含標量處理單元(SPU)、標量存儲單元(SM)的標量部件與包含向量處理單元(VPU)、向量存儲單元(AM)的向量部件以及取指派發單元構成。其中:取指派發單元可以同時為標量部件與向量部件派發指令;標量處理單元主要負責標量數據處理與程序流控制,同時負責控制向量部件的運行;向量處理單元負責高密度數據的并行計算任務。向量處理單元由16個同構的向量處理引擎(VPE)構成,VPE內部集成3個同構的浮點乘加器(VMAC),標向量數據通過標向量協同單元(SVR)進行數據共享,可以通過SVR共享寄存器使用廣播操作將一個標量寄存器的值傳遞到16個向量寄存器中[9]。GPDSP核內包含存儲器直接訪問部件(DMA),支持包括點對點、廣播、二維索引傳輸等靈活高效的傳輸方式,使得GPDSP能夠適應更多的應用需求。

圖1 GPDSP結構Fig.1 GPDSP structure
GPDSP架構具有典型的VLIW特征,與傳統處理器相比,為了增加運算性能并且簡化加速器設計,GPDSP加速核不具備動態指令分配的特性,而是將包含一條或多條指令的完整的指令包分發給確定的功能單元,指令的功能單元分配、排布優化與打包等任務通過編譯器的指令調度模塊完成,編譯器的指令調度優化性能也直接影響GPDSP的硬件性能釋放率。同時為了提升數據并行處理能力,GPDSP融合了SIMD的架構特征,在指令集設計上提供了豐富的向量指令,加速器內部的向量處理單元為向量指令的實現提供了良好的硬件支持,編譯器的支持能夠讓用戶更好地使用GPDSP向量部件,有效降低了用戶的開發難度。
LLVM編譯架構由編譯前端、代碼優化器及編譯后端三部分組成[10],如圖2所示。其中,編譯前端負責將高級語言(如C、C++、Python等)編寫的程序轉化成LLVM特定的中間表示形式LLVM IR;代碼優化器主要對程序進行動態、靜態分析及代碼優化工作;編譯后端負責將優化后的MI形式指令翻譯成目標機器(如x86、ARM、RISCV等)的匯編代碼。LLVM編譯框架中包含許多優化遍[11],包含指令調度、寄存器分配等,優化遍能夠極大程度地影響編譯器最終的性能。

圖2 LLVM編譯器架構Fig.2 LLVM compiler architecture
然而,在開源LLVM編譯器框架支持的眾多處理器中,少有與GPDSP架構類似的,指令調度等關鍵優化遍也無法滿足GPDSP使用需求。由于GPDSP采用VLIW+SIMD的異構架構,擁有大量的向量化資源,且用戶程序多為數據密集型程序,執行效率極其依賴編譯優化效果,因此有必要對GPDSP提供向量與指令調度支持。本文針對GPDSP芯片向量資源豐富、不包含動態調度硬件的特點,設計并優化支持GPDSP架構的指令調度策略,并提供向量支持。
指令調度是編譯器在IR層進行程序優化的一種技術,目的是通過調整指令順序提高指令層上的并行度,使得程序在處理器上能夠高效運行。現有LLVM指令調度策略是面向通用處理器CPU設計的,開源LLVM編譯框架中僅有高通針對VLIW架構做出支持與優化,但高通與GPDSP架構相差較大,對GPDSP架構后端支持僅有借鑒意義,因此,本文結合GPDSP架構特點設計專用于GPDSP加速核的指令調度模塊。指令調度模塊設計包含寄存器壓力感知調度與靜態功能單元分配2個部分:寄存器壓力感知調度基于pre-RA-sched設計,主要通過靜態分析程序信息預測寄存器壓力值,從而指導指令調度,獲取更優的調度效果;靜態功能單元分配基于post-RA-sched設計,負責將功能單元在編譯階段靜態分配給每一條指令,通過沖突檢測機制保證功能單元分配的正確性,從而保證程序正確執行,此部分是指令并行執行的基礎。
寄存器壓力感知調度主要是在MI-scheduler過程中,通過程序靜態分析的方式預測寄存器壓力,從而指導指令調度的進行。指令調度與寄存器分配是一對相互矛盾的NP困難問題,主流的優先級調度方法往往會產生一定程度的寄存器溢出,而在有大量高密度數據并行計算的GPDSP高性能加速器上,這一矛盾表現得更為明顯。采用峰值寄存器壓力感知方法(PERP)[12],結合GPDSP結構特點設定代價函數,并通過蟻群優化(ACO)[13]算法實現寄存器壓力感知調度,可以有效減少寄存器溢出數量,提升程序執行性能。寄存器壓力感知調度流程如圖3所示。

圖3 寄存器壓力感知調度流程Fig.3 Register pressure-aware scheduling process
寄存器壓力感知調度主要由兩部分組成:一是基于PERP的寄存器壓力感知;二是基于ACO算法的指令調度。
2.1.1 寄存器壓力感知算法
在指令調度過程中,通過def-use鏈獲取每條指令的寄存器定義及使用信息;在每一次最優節點選取之后,獲取該節點的寄存器使用信息,分別將def和use信息放入DefList、UseList中,通過對DefList、UseList 2個隊列的信息進行分析與處理,當CurrCycle值變化時計算當前周期的寄存器壓力ERP,CurrCycle值是SchedBoundary類下的一個unsigned類型變量,被用來表示調度周期,使用getCurrCycle()方法獲得。PERP則是每個基本塊內所有周期中最大的寄存器壓力值(ERP)。以Topdown調度方式為例,具體算法描述如算法1所示。
算法1寄存器壓力感知算法
輸入最優節點SU(SUnit類型),當前CurrCycle
輸出當前周期寄存器壓力值ERP
1.RPValue ComputeRP(SU,CurrCycle){
/*獲取當前SU對應的SDNode*/
2.SDNode MIScheNode = SU->getNode();
/*獲取當前SUnit使用和定義的值的列表*/
3.UseList=MIScheNode->getOperand();
4.DefList=MIScheNode->getValue();
/*將DefList插入活躍值列表LiveVRegList中*/
5.LiveVRegList.insert(LiveVRegList.end(),DefList.begin(),DefList.end());
/*將UseList中后續無使用且是在當前基本塊定義的值從LiveVRegList中刪除*/
6.For each I in UseList
/*檢查I是否在當前基本塊定義*/
7.If (LiveVRegList.contains(I) )
8.If (CheckForRelease)/*檢查I是否仍有使用*/
9.LiveVRegList.remove(I);
10.End If;
11.End If;
12.End For;
13.If (checkCurrCycle ());
/*若CurrCycle增加則計算ERP*/
14.ERP=LiveVRegList.size()-NumberPhyRegs;
15.}
算法1實現了對每個周期的寄存器壓力感知,當前基本塊的寄存器壓力PERP則是每個周期寄存器壓力ERP的最大值。
2.1.2 指令調度算法
通過ACO算法,在空間中搜索出大量滿足依賴的調度方案,根據結合GPDSP架構特點設定的代價函數,每次迭代選出代價函數最小的調度方案保存,達到最大迭代次數后,輸出最優調度方案。該算法由3層循環組成,最外層循環與中層循環是重復的迭代搜索過程,最外層循環每次迭代都會實時更新參數信息,最內層循環是基本塊調度循環,一次循環生成一種調度方案。以TopDown調度方式為例,指令調度算法描述如算法2所示。
算法2指令調度算法
輸入基本塊MBB
輸出近似最優調度方案
1.Scheduler.ACOSchedule(NumRegionInstrs){
/*初始化初始信息素濃度、信息素濃度矩陣、信息素揮發速率等參數信息,初始化DAG,構造原始調度隊列ACOTopRoots*/
2.For(i=0;i<149;++i)/*設定最大迭代次數為150*/
3.For(j:Ant)/*蟻群大小設定為50*/
4.TopRoots=ACOTopRoots;
/*拷貝原始調度隊列*/
5.While(true)/*無未調度SU,退出循環*/
/*若可用隊列available中無SU,返回空,退出當前調度;若可用隊列available中只有一個SU,返回該SU;否則按照可用調度路徑上信息素濃度大小,使用輪盤賭法選出一個SU進行調度*/
6.SUnit*SU = SchedImpl->pickNoedwithACO
/*將返回的SUnit保存*/
7.j->ScheduleList.push_back(SU);
/*更新調度隊列,從available中移除SU并釋放SU的所有后繼,更新available隊列與pending隊列*/
8.updateQueues(SU,IsTopNode)
/*計算當前周期寄存器壓力*/
9.CurrERP=ComputeRP(SU,CurrCycle)
10.j->ERPList.push_back(CurrERP);
11.End While;
12.End For;
/*遍歷所有調度方案,計算代價函數,返回最優調度方案BestScheduleList*/
13.BestScheduleList=setbest();
/*根據每個調度方案的代價函數值更新信息素濃度矩陣,代價函數值越低,信息素濃度增加越多;代價函數值越高,信息素濃度增加越少*/
14.updatepheromone()
15.End For;
/*根據最優調度方案進行調度*/
16.For(SU:BestScheduleList)
17.ScheduleMI(SU,IsTopNode);
18.End For;
19.}
PERP感知方法通過對程序的靜態分析得到基本塊的最大寄存器壓力,GPDSP高性能加速器結構特點與應用場景決定了用戶程序通常包含高密度數據并行計算,大多數應用程序在調度過程中存在多寄存器壓力峰值的情況,調度時僅僅考慮最大寄存器壓力在多數情況下不能得到良好的效果。因此,設定式(1)、式(2)所示代價函數,整體考慮寄存器壓力分布與峰值寄存器壓力對程序帶來的影響。

(1)

(2)
其中:S表示調度長度;ω表示寄存器壓力權重,該值越大,表示單位寄存器壓力對程序性能的影響越大,通過調整ω的值可以使程序偏向最小化調度長度或最小化寄存器壓力;Pmax表示峰值寄存器壓力;Perp表示單位周期的寄存器壓力;φ表示峰值寄存器壓力在整體寄存器壓力中的占比,φ值越高,表示峰值寄存器壓力對程序性能的影響越大,φ值越低,表示整體寄存器壓力對程序性能的影響越大。
靜態功能單元分配由后端描述、沖突檢測、功能單元分配3個模塊組成。其中:沖突檢測模塊主要負責檢測是否有可用功能單元、指令是否可分配功能單元等;功能單元分配模塊負責將可用功能單元與指令匹配;后端描述模塊保存指令的指令執行進程表與調度約束等調度信息。
2.2.1 后端描述
后端描述文件FTSchedule.td主要保存所有指令的調度信息以及與GPDSP加速器硬件相關的調度約束。與RISCV、ARM架構相比,GPDSP需要明確指令在執行過程中對功能單元與讀寫端口的占用信息。因此,重構LLVM的調度信息與使用方式,增加對讀寫占用的區分,具體格式如下:
InstrItinData
其中:Instr表示指令名稱;FuncUnitA、FuncUnitAw、FuncUnitB、FuncUnitBw表示所需的功能單元,使用w后綴對讀寫端口的占用進行區分,默認沒有w后綴表示占用功能單元讀端口;CycleA、CycleC表示占用對應功能單元的周期數;CycleB表示當前周期執行完后輸出指令執行結果所需要的周期數;Index表示在輸出指令之后讀取或寫入每個操作數所需要的周期數,默認IndexA為目標寄存器。
2.2.2 沖突檢測模塊
通過沖突檢測保證功能單元分配的正確性,每個Cycle都會對需要發射的每條指令SUnit執行沖突檢測模塊。輸入為指令SUnit,輸出為無沖突nohazard或有沖突havehazard,檢測流程如圖4所示。每次執行首先會從AvailableQueue隊列中拿出一條指令輸入,通過getInstrDesc方法從后端描述文件FTSchedule.td中獲得該指令的指令流水信息,從而獲得執行該指令所需要的功能單元、讀寫端口以及占用的周期數,若該指令是偽指令或沒有指令執行進程表,則直接返回nohazard。遍歷所有功能單元,通過RequiredScoreboard得分板獲得當前未被使用且可被分配的功能單元并存儲到FreeUnits中。沖突檢測時優先進行讀沖突檢測,不存在讀沖突時再進行寫沖突檢測。讀寫沖突須同時滿足條件返回nohazard,該功能單元可被分配。

圖4 沖突檢測流程Fig.4 Conflict detection process
2.2.3 功能單元分配模塊
該模塊的目標是使所有的指令在滿足依賴關系的情況下完成功能單元的分配。所有AvailableQueue隊列中滿足沖突檢測的指令會執行功能單元分配并調度,并將指令從AvailableQueue隊列中移除。若指令不滿足沖突檢測,即對該指令不存在可供分配的功能單元,則將該指令放入PendingQueue隊列中,等待下一次調度。當AvailableQueue為空時,Cycle加1,重新更新所有隊列,開始下一次調度。
GPDSP高性能加速器為向量指令實現提供了良好的硬件基礎,用戶可以使用向量指令為程序帶來可觀的性能加速,這需要從編譯器層面實現向量指令支持,為用戶提供向量指令接口。向量指令支持主要由向量指令實現、向量指令接口映射與匯編指令生成3個部分組成。其中:向量指令實現主要是面向硬件的向量指令集定義;向量指令接口映射主要負責將用戶使用的向量接口通過builtin接口映射為intrinsic接口;匯編指令生成負責將intrinsic接口映射為LLVM IR并匹配對應的向量指令,該部分與標量指令采用相同的指令選擇算法,參考LLVM指令選擇匹配模板即可,無需特殊處理與設計。因此,向量指令支持重點將放在向量指令實現與向量指令接口映射這2個部分。
為實現向量指令支持,新增了如表1所示的硬件支持的8種數據類型,所有數據類型的數據寬度均為1 024 bit。

表1 新增向量數據類型 Table 1 New vector data types
根據GPDSP指令集在后端描述文件中定義向量指令,以下給出定義向量指令的具體示例:
class 1OP_2OP_vrrr
其中:1OP_2OP_vrrr表示向量指令模板,具體向量指令的實現需要繼承對應的向量指令模板;funct10表示指令的操作碼,對應指令集中指令的機器碼;InsnCycle表示指令調度模式;opcodestr表示指令的名稱;u表示指令所需的功能單元;FTInstrAVR表示指令類型,包含對應的指令功能信息。
根據GPDSP指令集,定義如表2所示的向量訪存、向量運算等9種類型向量指令。

表2 新增向量指令 Table 2 New vector instructions
考慮到給用戶提供一套規整的向量指令使用接口,且與GNU編譯器集合(GCC)提供的向量接口保持高度統一,有效降低用戶開發難度,提高用戶編程友好性,采用如圖5所示的多級接口設計方案,基于GPDSP指令集,設計builtin接口與intrinsic接口,并將builtin接口封裝提供給用戶。

圖5 向量指令接口設計方案Fig.5 Design scheme of vector instruction interface
每個builtin接口都具有唯一確定的編號。通過封裝的形式,將builtin接口封裝為與GCC一致的向量指令接口提供給用戶使用。builtin接口與intrin接口使用編寫的映射規則進行映射,每一個builtin接口都通過統一的接口編號綁定到對應的intrinsic接口。
映射規則的實現是通過在機器描述文件intrinsic.td中編寫每一個intrinsic接口的記錄,該記錄維護builtin接口到intrinsic接口的索引,由tablegen工具自動生成C++轉換程序,實現接口的映射。以向量加法為例,映射規則設計如下:
def int_vec_add_v:GCCBuiltin<"__builtin_vec_add">,Intrinsic<[llvm_lvs_int_ty],[llvm_lvs_int_ty,llvm_lvs_int_ty],[IntrNoMem,Commutative]>
其中:__builtin_vec_add為綁定的builtin接口;int_vec_add_v為對應的intrinsic接口內部名稱;llvm_lvs_int_ty表示該加法操作的2個操作數都必須為lvector signed int類型數據。
特別地,部分源操作數包含立即數的指令,該類型的指令在匹配指令接口時,會由于重載導致立即數類型轉換為變量,無法匹配對應的指令接口。針對該類型指令,通過添加廣播操作,將立即數廣播到寄存器中,以匹配不含立即數類型的向量接口,實現相同的指令執行效果。
本文提出的編譯器設計與優化基于LLVM 12.0的版本進行代碼功能實現,采用GPDSP高性能加速器[14]作為硬件測試平臺,開展LLVM編譯器功能測試與性能測試,重點包含指令調度與向量指令接口2個方面。
選用GCC testsuite與SPEC CPU 2017 2個測試集對改進后的指令調度模塊進行功能驗證與性能測試;基于GPDSP增加的向量指令接口,額外增加部分測試代碼進行向量指令功能驗證與性能測試。所有測試代碼均由改進后的LLVM/Clang 12.0.0編譯生成。
4.2.1 指令調度模塊功能測試與分析
為驗證設計編譯器的正確性,對GCC testsuite中的所有測試程序與SPEC CPU2017標準測試集中編程語言不包含Fortran的30個測試題進行測試。以-O0優化級別下通過率為基準,GCC testsuite測試集在各優化級別下的測試結果如表3所示,可見,編譯器在-O1優化級別下通過率為98.31%,在-O2、-O3、-Os優化級別下通過率為98.73%。編譯及執行未通過測試用例的主要原因是部分inline以及優化處理與GPDSP架構不兼容,這些測試用例后續不再引入測試集進行結果展示。

表3 GCC testsuite測試結果 Table 3 Test results of GCC testsuite
SPEC CPU2017測試集的測試結果如表4所示,可見,標準測試集中整型與浮點型2類測試例共29道測試題均能正常執行通過。

表4 SPEC CPU 2017測試結果 Table 4 Test results of SPEC CPU 2017
通過GCC testsuite與SPEC CPU 2017這2個測試集測試結果的對比分析可以得出,改進指令調度模塊后LLVM編譯器能夠正常生成正確的匯編程序且程序能夠正確執行,指令調度模塊功能驗證通過。
4.2.2 向量指令接口功能測試與分析
基于GPDSP指令集,編寫部分包含向量指令接口的測試代碼,通過改進后LLVM編譯器編譯生成可執行文件,并使用GPDSP高性能加速器運行,統計對應向量指令匯編代碼生成情況與程序執行情況,測試結果如表5所示,可見,所有向量指令接口都能生成對應的匯編指令,指令匹配正確,程序執行結果正確,向量指令接口功能驗證通過。

表5 向量指令接口功能測試結果 Table 5 Function test results of vector intruction interface
4.3.1 整體性能測試與分析
選用GCC testsuite、SPEC CPU 2017測試集對編譯器整體性能進行測試,明確性能邊界。不同優化選項下采用的編譯優化手段各不相同:-O0優化選項下不執行任何編譯優化,程序順序執行;-O1優化選項下會執行一些不增加大量編譯時間的簡單優化,可能會導致指令并行化;-O2優化選項下會執行絕大部分優化,最大程度地挖掘指令并行性能。在實現支持靜態功能單元分配的指令調度后,為GPDSP執行并行指令提供了軟件基礎,保證了GPDSP指令并行執行的正確性。
通過對比GCC testsuite測試集在-O0、-O1、-O2優化級別下的執行速度,對編譯器的整體性能進行評估,測試結果如圖6所示。以-O0優化級別為基準,-O1與-O2優化級別均能得到較少的程序執行時間,其中:-O1優化級別下平均加速比為3.082,最高加速比為38.174;-O2優化級別下平均加速比為4.539,最高加速比為44.644。

圖6 GCC testsuite整體性能測試結果Fig.6 Overall performance test results of GCC testsuite
圖7所示為SPEC CPU 2017測試集測試結果,通過對比SPEC CPU 2017測試集在-O0、-O2優化級別下的執行速度,評估編譯器的整體性能。由圖可見:以-O0優化級別為基準,浮點測試平均性能加速比為4.49,最高加速比為14.03;整型測試平均性能加速比為3.24,最高加速比為5.94。

圖7 SPEC CPU 2017整體性能測試結果Fig.7 Overall performance test results of SPEC CPU 2017
4.3.2 寄存器壓力感知性能測試與分析
基于寄存器壓力感知的指令調度方法能夠在程序編譯時靜態分析寄存器壓力值,通過代價函數選擇最優調度方案,有效減少寄存器溢出數量,提升程序性能。寄存器壓力感知調度對數據密集型程序更為敏感,因此,采用SPEC CPU 2017測試工具進行性能測試,對比LLVM指令調度器默認調度方法與寄存器壓力感知調度方法的加速性能,編譯優化選擇-O2-mllvm-enbale-regpressure-mllvm-debug-only=machine-scheduler,ref規模,測試結果如圖8所示。可以看出,使用寄存器壓力感知調度后程序可以得到明顯的性能改善,最高加速比為1.06,浮點測試平均加速比為1.024,整型測試平均加速比為1.016。

圖8 寄存器壓力感知性能測試結果Fig.8 Performance test results of register pressure-aware tests
4.3.3 向量指令接口性能測試
使用向量指令接口進行編程,通過編譯器生成對應向量匯編指令,能夠極大地減少代碼體積,提升程序執行效率。本文基于GPDSP向量指令集,增加了部分函數級測試代碼,對比使用向量指令前后的性能與代碼體積差異,結果如表6與圖9所示。從表6中可以看出,在使用向量指令后,程序執行包數量平均減少95.41%,vsip_vcmagsq_f指令程序執行包數量減少率最高,減少率為98.27%。從圖9中可以看出,使用向量指令后,所有程序都獲得了不同程度的性能提升,平均性能提升率為97.1%,最高性能提升率為98.7%。

表6 向量程序體積測試結果 Table 6 Vector program volume test results

圖9 向量程序運行時間測試結果Fig.9 Test results of vector program runtime
編譯器是用戶程序開發最重要的工具之一,能夠為用戶提供多種優化手段,如何利用編譯器為用戶降低程序開發難度并且帶來更好的優化效果,始終是編譯器領域研究者所關注的問題。
在高性能計算領域,用戶程序大多具有數據密集型與計算密集型的特點,而用戶使用高度抽象的高級編程語言開發程序時無法從細粒度的指令集層面考慮熱點代碼片段的數據訪存開銷與硬件資源利用率,因此,本文結合當前高性能計算領域研究熱點,從編譯器的角度出發,對未來優化方向進行討論和探索:
1)組合優化[15-16]。現有編譯器框架的優化方式大多是分開進行的,問題在于多種優化方式之間可能存在隱形的影響,例如指令調度與寄存器分配。指令調度以最大化指令級并行(ILP)為目標,在寄存器分配階段時可能導致寄存器溢出,需要將多余的變量值寫回內存中,從而產生額外的訪存開銷,影響程序性能。解決這類問題的主要思路是通過組合優化的方式,將多種優化方式組合在一個優化中,從全局的角度進行優化,但這會導致極大的時間開銷。目前組合優化的效果并不理想,需要探尋新的優化方式與優化組合,以達到更好的性能提升與更低的時間開銷。
2)自動向量化[17-18]。相比于手動編寫SIMD向量程序,自動向量化能讓程序員繼續采用串行思想進行編程,而不用考慮SIMD擴展部件的功能與特點,減少程序員編程難度與工作量。目前,自動向量化技術還存在許多不足之處,值得進一步研究,例如馮竟舸等[19]提出的引入動態規劃、整數線性規劃等組合優化方法進行并行度的選擇、引入同步多線程(SMT)等理論方法解決向量化分組問題、對SIMD擴展部件進行特異化設計等方向都是未來可繼續研究的方向。
3)自動調優[20-22]。GCC和LLVM中都包含有大量的優化方式,通過-O參數設置選擇對應的優化級別是當下普遍的選擇[23-24],然而固定組合優化方式并不能總是帶來更好的程序執行性能,不同參數因子、優化選項、優化選項執行順序對程序的性能都會帶來不同的影響。通過迭代編譯或機器學習的方式在大量訓練下進行自適應的因子選擇、自動優化編譯選項、內聯決策和分支概率預測等能為編譯器帶來更多性能提升的空間[25]。
本文結合高性能加速器與其加速核GPDSP的結構特點,提出了支持功能單元靜態分配與寄存器壓力感知的指令調度策略,為高性能加速器的指令并行執行提供軟件支持,減少數據密集型應用普遍存在的寄存器溢出,同時使用向量intrinsic方法對GPDSP向量指令集提供了支持。實驗結果表明:優化后指令調度在功能上能夠實現對高性能加速器的良好支撐,相對于LLVM -O0優化方法,使用GCC testsuite測試在-O2優化選項下能夠實現平均4.539的加速比,使用SPEC CPU 2017浮點測試與整型測試在-O2優化選項下分別實現了平均4.49、3.24的加速比;使用寄存器壓力感知的指令調度在浮點程序與整型程序上分別實現了平均1.024、1.016的性能加速比;相對于標量程序,使用向量指令接口實現的向量程序在執行包數量上平均減少95.41%,平均性能加速比為35.2,降低了用戶編程難度,提高了用戶友好性。未來將繼續優化寄存器壓力感知調度方法,探索新的寄存器壓力感知策略,并進一步支持向量程序指令調度,提升向量部件資源利用率。同時,將持續研究編譯器層面理論方法,從軟件角度挖掘高性能計算的優化空間。