俞茂學,賈東寧,2,魏志強,2,許佳立,馬廣浩
(1.青島海洋科學與技術試點國家實驗室,山東 青島 266237;2.中國海洋大學信息科學與工程學院,山東 青島 266100)
超級計算機是當前信息時代世界各主要國家競爭激烈的科技制高點,是一個國家科技水平和綜合國力的重要標志。中美競爭愈演愈烈,超算作為第四次工業革命的重大基礎設施,已成為兩國角力的主要戰場之一,其國產化已經上升為國家戰略。我國國產化處理器以異構眾核的申威SW26010為代表,該處理器包含4個運算核組CG(Core Group),每個核組包括1個內存控制器MC(Memory Controller)和64個運算核心,理論峰值性能達到125.4 Pflop/s,核心工作頻率1.5 GHz,性能指標世界領先[1]。該芯片的架構面向高性能計算,通過統一指令系統、統一執行模型和支持一致性的主存共享,實現了異構核心的深度融合[2]。在基礎軟件生態方面,已經部署完成了神威·太湖之光操作系統神威睿思(RaiseOS 2.0.5),支持Linux系統,支持國際標準的編程語言,如C/C++ /Fortran編譯器以及相適配的向量工具、基礎數學庫等。此外,在并行編程工具方面,主要采用神威OpenACC,兼容OpenACC 2.0標準,并添加了部分定制功能[3]。該國產芯片打破了國際對我國的技術封鎖,實現了軟硬件全部國產定制開發。
異構眾核架構雖然具有諸多優勢,但在單個計算結點內集成了大量不同類型的處理器核心,使得體系結構更為復雜,這給程序設計特別對于多核架構的遺產代碼移植帶來了挑戰。另一方面,當前人工智能與大數據領域應用軟件九成以上是基于x86和GPU開發的,海量的遺產代碼對移植工作造成了較大困難。采用人工進行并行程序編寫和優化往往具有很高的運行效率,但存在成本高、效率低等問題。王一超等人[3,4]在神威·太湖之光上完全手工移植了科學應用GTC-P,代碼效率高,但成本也較高。當前,國產眾核架構的并行編譯系統無法支持眾核C++ 編譯,導致大量C++ 遺產程序無法利用神威·太湖之光的從核加速優勢,阻礙了國產眾核系統的發展步伐。
在眾核結構的并行化編譯領域,面向GPU平臺,研究人員針對并行編譯系統的自動移植做了大量工作。Matsumura等人[5]提出了基于OpenACC的普適于多GPU的源-源轉換器,通過識別GPU類型生成不同的OpenACC制導語句。Lashgar等人[6]提出了一種將OpenACC自動翻譯成OpenCL(Open Computing Language)和CUDA(Compute Unified Device Architecture)的源-源翻譯工具,將輸入代碼進行歸一化處理后,經過中間層XML格式分離出主機代碼和加速代碼,對加速代碼進行轉換,形成CUDA和OpenACC代碼。Lee等人[7]實現了將面向共享存儲結構的OpenMP程序轉換成GPGPU程序的自動轉換和優化框架,能夠將已有的OpenMP程序移植到GPU眾核平臺。國內研究主要集中在利用clang編譯前端對OpenACC進行源碼轉換的工作上。江霞[8]提出利用clang編譯前端對Intel Xeon Phi協處理器進行自動OpenACC轉換。本研究過程中采用的開源ANTLR(ANother Tool for Language Recognition)是一個功能強大的解析器生成器,用以讀取、處理、執行或翻譯結構化文本,被廣泛用于構建語言、工具和框架,通過利用其強大的語法規則支持,源碼轉換的難度被大幅降低。
C++ 作為面向對象的語言,其關鍵語言特性為類和對象、模板和泛型設計、標準庫及并發設計[9]。本文針對這幾個關鍵特性,基于ANTLR的源碼翻譯工具,探討其自動轉換成C語言的原理和可實現性。針對科學運算的領域代碼特點,對于較少使用或者轉換復雜的STL(Standard Template Library)庫函數等進行自動標注,構建了一整套C++ 轉換成C語言的輔助轉換框架,協助程序開發人員實現存量遺產代碼的快速移植。
本文首先介紹了國產眾核的并行編譯系統、ANTLR架構、面向對象和面向過程語言的特點及轉換要素。在此基礎上,詳細論述了源碼轉換的關鍵技術,最終將轉換后的代碼在神威·太湖之光國產眾核系統上進行了轉換代碼的試運行。
神威·太湖之光超級計算機上使用的神威SW26010芯片架構如圖1所示,使用64位自主申威指令系統,采用片上融合異構的體系結構,每個核組內64個從核按照8×8的mesh拓撲結構,由片上內部網絡互連[2]。眾核處理器體系結構不僅對科學工程計算具有較高的效能和較好的適應性,對雙精度、單精度的矩陣計算的支持同樣能夠在一定程度上滿足人工智能的關鍵計算需求[10]。

Figure 1 Architecture of Sunway many-core processor
神威·太湖之光的軟件系統采用定制的64位Linux操作系統,由基礎編程語言、并行編程語言和接口、用戶使用環境、基礎編程環境和工具等4部分組成,如圖2所示。并行編程支持與國際接軌的并行編程標準,包括MPI 3.0、OpenMP 3.1、Pthreads、OpenACC 2.0,支持消息并行編程模型、共享并行編程模型、加速并行編程模型,滿足科學與工程計算課題開發和移植的多樣性需要[11]。

Figure 2 Software environment composition of Sunway TaihuLight
并行開發主要采用2種架構,MPI+Athread和MPI+OpenACC*。其中,Athread直接控制LDM和DMA,可以控制使用所有的硬件功能,需要編寫主核、從核程序,改造時間較長,需要性能細節人為可見、可控,需要深刻理解架構特點,掌握優化方法。如劉徐等人[12]對遺傳算法的并行參數自動尋優的研究。OpenACC*可以在短時間完成移植,并獲得可觀的加速性能,保持跨平臺的可移植性,在“神威·太湖之光”上獲得了廣泛應用。面向申威SW26010異構眾核處理器結構特點,OpenACC*是在OpenACC 2.0的基礎上進行適當精簡和擴充構建而成的,以編譯指令的方式提供眾核編程所需的語言功能。編譯器為國產定制的SWACC/SWAFORT。整個編譯系統的支持情況如表1所示。

Table 1 Sunway basic compilation command
面向對象程序設計語言的核心思想是數據抽象、繼承和動態綁定。通過使用數據抽象,可以將類的接口和實現分離;使用繼承,可以定義相似的類型并對其相似關系建模;使用動態綁定,可以在一定程度上忽略相似類型的區別,而以統一的方式使用它們的對象[13]。面向過程程序設計基于結構化程序設計思想,強調程序結構規范為順序、選擇和循環3種基本結構。以算法為核心,把數據和操作分離。按照解決問題的過程劃分模塊,集中處理數據,運行效率高。但是,代碼的重用性和可擴展性比較差。C語言作為面向過程的語言更為普及地應用于并行計算。根據2種完全不同架構的語言特點,其轉換要素主要聚焦于類的封裝、繼承和多態,模板和泛型設計,標準庫和并行設計。
本文構建了C++轉換成C語言的輔助開發框架,建立C++轉換后的C代碼框架、增加轉換標注以及建立C轉換常用函數代碼,加速開發人員的移植速度。類封裝和繼承的C轉換采用C語言的結構體作為類轉換的目標,識別基類和繼承類,并賦予不同的結構體命名規則。多態的轉換利用多態規則的識別,通過類名和函數名對多態函數進行區分和重定義。借鑒編譯器處理模板和實例化的方法,建立模板和實例化的關系,并通過2次語法樹遍歷解析,實現了C語言轉換。C++作為典型的面向對象的語言,其提供了豐富的STL庫,對于STL庫的解析,本文創建了自動標注及輔助部分C函數對應實現的方式。通過以上設計,表明了輔助快速移植的可行性和有效性。
ANTLR是以PCCTS(Purdue Compiler Construction Tool Set)為基礎構建的開源工具,致力于解決編譯前端的所有工作。研究表明,ANTLR的語法可以用來定義模板語言的詞法記號和語法規則[14]。該工具可以對Java、C/C++、C#等語言進行語法描述,自動生成詞法分析器和語法分析器,將開發人員輸入的程序根據相對應的編程語言語法規則轉換為抽象語法樹。在ANTLR強大的支持下,大大降低了源碼轉換的難度。首先,采用詞法分析器處理字符序列,并把產生的詞法符號交給語法分析器。其次,基于以上信息開展語法檢查,并構建一棵語法分析樹。在這個過程中,使用的ANTLR類包括:CharStream、Lexer、Token Stream、Parser和ParseTree。通過TokenStream,可以把詞法分析器和語法分析器進行連接[15],實現類之間的交互,如圖3所示。

Figure 3 Interaction mode between ANTLR classes
在開源的C++語法規則CPP14.g4的基礎上,使用ANTLR對C++源碼進行詞法分析、語法分析和語義分析,生成抽象語法樹,結合ANTLR自帶的ParseTreeProperty類,實現每個子節點內容的C轉換,最終形成目標C語言的解析樹。
依據C++的代碼特點和抽象語法樹的節點路徑關系,本文將C++代碼分為5部分進行解析:基類聲明、繼承類聲明、函數定義、函數參數和main函數。針對以上不同部分,構建區別化的解析轉換流程。
對于C++特有的泛型設計、引用、運算符重載等語法特點,本文采用2次語法樹遍歷解析的方式進行轉換。在第1次語法樹遍歷中,獲取模板和實例化的對應關系,引用變量名稱等關鍵數據,同時構建通用的Utility類,保存關鍵解析數據。在第2次語法樹遍歷中,使用先前獲取到的關鍵解析數據實現實例化的C語言轉換。
以科學計算領域常用的STL庫為研究對象,進行STL庫的轉換。對頻繁調用的STL庫,采用預先完成轉換的C語言函數進行替代和重構。對不常用的STL庫函數或者C++的特殊用法,創建自動標注體系輔助開發人員進行快速有效識別,并進行替換。
整個架構設計如圖4所示。

Figure 4 Overall architecture of source code transforms
C++的類轉換成C的結構體聲明,其實現步驟如下所示:
(1)基類和繼承類的劃分。繼承類的C轉換需要將對應基類作為結構體指針變量,從而實現C++的繼承特性。
(2)類成員通用函數解析與轉換。在C++中,類成員函數中可以直接調用類中的其他成員變量,但對于C語言而言是非法的。因此,需要在C語言所有類成員函數的參數中增加此類的結構體指針變量,用于類成員變量的相互調用。
(3)類的構造函數解析與轉換。構造函數名稱需要與類名稱進行區別化處理,增加Str前綴,且指針化(*Str+函數名)處理。針對構造函數使用初始化列表的過程,需要進行2部分轉換。首先,對構造函數的聲明進行先行轉換;其次,在函數定義過程中進行初始化列表轉換。
(4)類的虛函數和析構函數的解析與轉換。首先,通過virtual關鍵字識別類的虛函數。其次,采用類名+變量名組合的形式進行函數名稱的轉換。然后,在虛函數參數中增加結構體指針變量,以解決類的多態轉換問題。
(5)類成員變量的解析變換。針對C++中特有的變量,例如String、vector、list等,需要進行類型轉換和標注,以滿足轉換需求。
類聲明的C結構體轉換實例如圖5所示。

Figure 5 Example of class structure transforms
函數定義是源碼轉換的重要環節,本文采用預先設定的范圍標簽進行區別化轉換。函數定義的解析分為2部分:函數頭和函數體。函數定義的轉換需要考慮類型因素。根據函數定義的特點,其類型主要包含類成員函數、構造函數、虛函數、析構函數、普通函數和main函數等。函數定義轉換的具體過程如圖6所示。首先,通過函數類型識別,查表獲得預先保存的函數頭。其次,在函數體轉換中,識別出構造函數的初始化列表,結合預先保存的類成員變量對初始化列表進行C轉換。解析函數體中的表達式的類型和關鍵字,進行對應的自動標注或C轉換。最后,組合解析后的函數頭和函數體,形成轉換后完整的函數定義。

Figure 6 C transform process of function definition
泛型程序設計是一種把算法程序從獨立的源程序中抽離出來的抽象編程機制,具有簡化程序、提高代碼可讀性與復用性的特點。模板作為泛型程序設計的核心部分,一直被用于開發可復用軟件,是C++最重要的語言特性之一[16]。模板作為泛型設計的一個典型應用,分為函數模板和類模板。其中,函數模板通過函數調用實現實例化,類模板通過將模板形參與實參實現綁定實現實例化。
為了便于模板的實例化轉換,本文在第1次語法遍歷過程中,實現了數據的結構化,其結構為:map〈〈模板,實例〉,類名(函數名)〉。在類模板數據結構化方面,由于其格式比較固定,一般出現在類的變量定義和初始化階段,數據結構化過程相對固定;在函數模板數據結構化方面,由于其實例化的表現形式較多,會出現多個函數模板嵌套調用的現象,導致數據結構化獲取不全。針對這種狀況,本文增加了map〈模板,函數名〉鍵值對的數據結構,以保證嵌套模板函數的完全實例化。
在第2次語法樹遍歷過程中,實現了模板的實例化轉換,其主要過程如圖7所示。

Figure 7 Instance process of template function
具體表現為:
(1)添加特殊標簽。對語法樹解析的變量類型入口trailing-type-specifier添加特殊標簽,同步對模板名和實例名增加相同的標簽,以解決對類或函數模板實例化時出現的誤替換問題。例如,模板名稱為D,實例化為float類型,如果代碼中有變量定義包含D,如 D Data = 0.1;則表達式會被誤替換為float floatata=0.1;類型增加了特殊標簽后,表達式變更為Dqnlm Data = 0.1;進行模板實例化替換時則杜絕了誤替換的問題。
(2)語法樹節點判斷。類的實例化導致實例化前后的類所處的語法樹節點發生變化,如圖8所示。因此,在Declaration節點中增加判斷,確保實例化后的C結構體聲明正確壓棧,解決了實例化后無法從下一級語法樹上正確獲取的問題。

Figure 8 Change of syntax tree after instantiation
(3)函數模板實例化??紤]到函數模板中嵌套調用函數模板的情況,將第1次遍歷過程中未找到實例化的函數模板統一保存,并集中處理。首先,對第1次遍歷中找到的實例化函數進行解析。其次,對此函數進行2次遍歷,使用預先保存的尚未實例化的數據結構〈模板,函數名〉進行查找,判斷是否存在新的實例化對象,并保存到newinstance數據結構中。最后,針對所有的未實例化的模板函數進行統一實例化,通過對數據結構newinstance的增加和刪除,實現所有模板函數的實例化。
(4)類模板實例化。首先,對類模板及類成員函數進行實例化。在此基礎上,建立實例化的類名稱和類成員函數的賦值對應關系。對實例化的類名和類成員函數進行鍵值對保存,以用于在結構體初始化時進行對應賦值。
需要說明的是,基于函數模板的STL庫是一類使用頻繁且封裝好的庫,因此該部分的實例化轉換采用預先完成轉換的C語言函數進行替代和重構。以cout標準輸出為例,匯總其輸出的格式化標記、格式操作符及格式化函數,并進行C語言Printf的對應轉換,實現了cout標準輸出的C轉換方式,如表2所示。
C++11的標準庫由11個子庫構成,包括3個新型標準庫和8個傳統標準庫。由于C++標準庫的實現主要依賴于模板,使得STL庫組件具有廣泛通用性的底層特征。STL庫由容器、算法、迭代器、仿函數和內存分配器組成,包含了諸多在計算機科學領域中常用的基本數據結構和基本算法,提供了一個可擴展的應用框架,高度體現了軟件的可復用性[17]。

Table 2 Transform key points for cout STL library
在C++到C語言的源代碼轉換過程中,STL庫的轉換是主要難點之一。本文基于對C++標準庫的理解,構建了自動標注體系,通過關鍵字和語義的識別,標注出需要替換的庫函數,便于程序員進行快速分析和開發,以實現STL庫的快速轉換。其中,本文構建的自動標注體系類別信息如表3所示。

Table 3 Category corresponding information of automatic marking system
本文選取經典的BableStream內存基準帶寬程序為實驗對象。該程序提供了基于GPU的內存傳輸速率的基準測試[18],編程語言為C++,支持包括OpenACC、OpenCL和OpenMP在內的多種并行架構,提供了4種主要的加速任務,由于具有跨平臺性及高可靠性,該程序被廣泛應用于并行計算領域。BableStream僅能支持Intel和PGI的編譯器,并且編程語言為C++,無法在國產眾核平臺上直接運行。本文對BableStream程序進行了輔助智能轉換,加速了代碼轉換的效率,轉換框架如圖9所示。

Figure 9 C language translation framework of BableStream
轉換后的代碼經過標注代碼修改后,在神威·太湖之光的編譯系統上可以完成編譯并運行,經過實際測試,自動轉換時間為7.9 s,轉換后的C代碼811行,標注代碼73行,手動對標注代碼修改后可以編譯通過并正確運行,修改后的代碼1 301行。目前智能轉換率為60%左右。
實驗平臺為神威·太湖之光異構眾核系統,其參數如表4所示[19]。

Table 4 Experimental environment
多核普通計算機的運行環境如下所示:
(1)Linux架構:x86體系結構64位;
(2)操作系統版本:Ubuntu 18.04.3 LTS;
(3)CPU信息:處理器:Intel(R)Core(TM)i5-10210U CPU @ 1.6 GHz;一共8個CPU,每個CPU 4核,緩存:6 MB;
(4)PGI的編譯版本:19.10.0。
C++在多核計算機上的運行結果如表5所示。

Table 5 Test results of BableStream on mutlicore computer
轉換后的C代碼在神威·太湖之光上的運行結果如表6所示。

Table 6 Test results of BableStream on Sunway TaihuLight
通過實驗對比分析發現,轉換后的性能數據較差,原因是由于BableStream的OpenACC版本和神威·太湖之光使用的版本存在較大差異,OpenACC的制導語句及加速代碼都是使用類C語言實現,不涉及本文的轉換內容。后續需要進一步對OpenACC的制導語句進行并行優化。
從數量和性能綜合來看,我國超級計算機的臺數比美國多一倍,但總運算性能和美國基本相當,說明我國超級計算機的平均性能和美國相比有較大差距,這是由于在當前超級計算機生態系統中,基于x86架構的CPU生態最為完善,操作系統、應用軟件和工具最多。我國基于申威CPU的超級計算機對應的軟件生態環境一直處于劣勢,急需加快建設速度。本文提出了一種基于國產眾核的C++輔助源碼智能轉換框架,基于開源軟件ANTLR對C++語言進行詞法、語法和語義分析并形成抽象語法樹,結合面向對象語言的關鍵特征,完成了C++代碼自動轉換成C代碼的架構,建立了部分STL庫函數的預先對應轉換代碼和自動標注系統,加快了源碼轉換的效率。
在轉換過程中發現了可持續優化的方面,如針對不同C++代碼的兼容性設計,對內存的使用需要更好地予以標注或自動轉換,轉換后函數的順序不一致導致出現編譯出錯的問題,C++的容器轉換通過智能方式優化,以及OpenACC的制導語句并行優化的智能轉換等都需要進行持續研究和優化。后續會持續深入研究轉換的穩定性和兼容性,以及并行優化的智能轉換。