從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

x86與ARM之爭,已經(jīng)貫穿了很長時間,過去一直是x86架構(gòu)比較受到市場和開發(fā)者的歡迎。但是自從移動互聯(lián)網(wǎng)、物聯(lián)網(wǎng)和邊緣計算興起之后,ARM似乎已經(jīng)找到最適合自己生存的土壤。

架構(gòu)之爭的平臺技術(shù)拐點,已然來臨。

現(xiàn)在,每個人手上都有一臺智能計算終端,移動應(yīng)用逐漸云化,5G 催生了云游戲的誕生;Web 應(yīng)用的加密性越來越重要,HTTPS 流量越來越大;大數(shù)據(jù)分布式并行計算成為主流等,這些都讓 x86 架構(gòu)的不足逐漸顯露出來。

所以才會有現(xiàn)在所遇到的情況,即不得不從x86上的應(yīng)用遷移至ARM上。但也正因為這是兩個完全不同的平臺,所以在遷移過程中會遇到各種各樣的問題。這也是DevRun 開發(fā)者沙龍–首期【鵬城實驗室 & 華為鯤鵬專場】中所重點講到的問題之一。

以下內(nèi)容經(jīng)由InfoQ編輯整理自DevRun 開發(fā)者沙龍【鵬城實驗室 & 華為鯤鵬專場】中張永正和楊少洪老師的分享。

鯤鵬軟件遷移概述和方法論

首先要搞懂:為什么要遷移?

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

上圖所示為程序執(zhí)行的過程和對應(yīng)的計算棧。任何一臺計算機都是由硬件和軟件組成的,類似于最底層的基礎(chǔ)物理原材料、晶體管、寄存器、微架構(gòu)等都屬于硬件層面。而軟件層面則特指由高級語言、匯編語言開發(fā)的應(yīng)用程序。要執(zhí)行這些應(yīng)用程序,需要底層CPU支持由匯編器形成二進(jìn)制的機器碼(由指令和數(shù)據(jù)組成)去運行。

因此就需要底層計算平臺能夠支持該CPU的指令才可以,這也是在x86和鯤鵬編譯的區(qū)別之處。

在x86和鯤鵬上編譯之后的指令差異是哪些?可以參照下圖左側(cè),顯而易見這是一套非常簡單的代碼,分別在x86和鯤鵬上編譯之后形成三點指令差異:

首先是匯編不同,x86上是兩條mov指令,通過把A和B的變量從內(nèi)存當(dāng)中取到寄存器,并將兩值相加,再由一條mov指令寫回內(nèi)存;鯤鵬上則是通過兩條ldr指令、一條add指令以及一條str指令完成整個過程。

其次是指令長度不同,x86上mov指令是24位的,ldr指令是16位的,在鯤鵬上則都是32位;第三則是寄存器不同,x86和鯤鵬處理器使用的向量寄存器不同,其向量指令級也存在差異。

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

鯤鵬處理器與x86處理器的指令差異

這也正是做遷移的原因,因為在x86上所編譯出來的應(yīng)用程序因為有以上三點的不同,因此無法在鯤鵬上直接運行。

從x86到鯤鵬,遷移五步走戰(zhàn)略

從大量的實踐中得以總結(jié)出一些規(guī)律和方法,主要分為以下5個步驟:

1、遷移準(zhǔn)備,主要以收集硬件信息和軟件棧信息為主;

在這期間,主要收集硬件和軟件信息。硬件方面的信息主要是收集芯片和服務(wù)器的型號,從而方便提供配置性能差不多的鯤鵬服務(wù)器;其次是收集軟件棧信息,主要分為操作系統(tǒng)、虛擬機、中間件、編譯器、上層依賴的開源軟件、商業(yè)軟件、業(yè)務(wù)軟件等信息。

2、遷移分析,對收集到的信息和軟件棧做初步分析,判斷是否真正需要遷移,評估遷移的工作量;

下圖左側(cè)就是一個非常完善的技術(shù)棧,底層有芯片,中間層為OS、虛擬機、編譯器等相對應(yīng)的運行環(huán)境。上層是業(yè)務(wù)軟件,分為開源、自研和商用軟件。

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

開源軟件的遷移相對較為簡單,其中一部分開源軟件在ARM上已經(jīng)被編譯好的包,直接下載即可。即便沒有現(xiàn)成的編譯成果,自行下載原碼進(jìn)行編譯也并不復(fù)雜方便;自研軟件的遷移需要注意語言類型的差異,編譯型語言是需要重新編譯之后才能運行在新環(huán)境上,但是對于解釋型的語言來說就沒有重新編譯的需要,只要更換所依賴的虛擬機就可以;商用軟件則較為麻煩一些,首先可以通過聯(lián)系廠商獲取它對應(yīng)ARM架構(gòu)下的軟件版本,如果沒有的話就需要尋找有類似功能的軟件做替換。

此外像運行環(huán)境、虛擬機、編譯器和操作系統(tǒng)這些也是要進(jìn)行替換,但是這些并非需要重新編譯,因為在華為云鯤鵬論壇內(nèi)有軟件倉庫,可以直接去軟件倉庫下載由鯤鵬官方所做的經(jīng)過驗證的版本。

3、編譯遷移,分析完成之后就可以著手遷移工作,主要分為代碼遷移和軟件包遷移;

遷移分析之后可以進(jìn)行代碼的編譯和打包,編譯主要涉及兩個到代碼遷移和軟件包遷移。

其中代碼遷移需要區(qū)分語言,像C/C++和指令級的差異是比較大的,因此在x86上編譯出來的應(yīng)用程序無法在在鯤鵬上直接使用,因此要在鯤鵬上重新編譯才可以。此外編譯型語言所涉及的修改點相對更多,因為代碼當(dāng)中有可能蘊含一些對指令級相關(guān)的宏定義或者功能性函數(shù);但是對于Java/Python這種解釋型語言來說就會簡單很多。如果是純Java/Python的程序,就無須做編譯,因為本身的虛擬機已經(jīng)對指令級進(jìn)行了屏蔽,只要更換虛擬機就可以。

對于軟件包遷移來說,首先需要掃描該軟件包是否存在依賴庫或者依賴的可執(zhí)行程序,這些庫和可執(zhí)行程序如果是用C語言寫的是需要重新編譯的,編譯之后重新把軟件包打包即可。

4、性能調(diào)優(yōu),驗證完成之后對性能指標(biāo)進(jìn)行測試,進(jìn)行性能調(diào)優(yōu);

由于大部分軟件對性能都有要求,因此在遷移完成之后需要對性能進(jìn)行調(diào)優(yōu),這里總結(jié)了【建立基準(zhǔn)-壓力測試-確定瓶頸-實施優(yōu)化-確認(rèn)效果】這五步法。

首先需要建立調(diào)優(yōu)基準(zhǔn),該基準(zhǔn)根據(jù)當(dāng)前的硬件配置、組網(wǎng)、測試模型來做綜合評估,以建立合理的條有目標(biāo);其次在調(diào)優(yōu)目標(biāo)建立后,通過壓測工具對軟件或系統(tǒng)進(jìn)行加壓,在加壓過程中暴露性能瓶頸,確定瓶頸之后對瓶頸進(jìn)行優(yōu)化;第四,注意在優(yōu)化過程中要及時記錄,因為優(yōu)化并不一定是正向的,出現(xiàn)負(fù)向優(yōu)化時需要及時回退;最后在優(yōu)化措施實施完成后,需要重新啟動壓力測試工具以確認(rèn)優(yōu)化效果。

這個過程并非一蹴而就,有可能其中某個步驟需要經(jīng)過多次循環(huán)之后才能達(dá)到既定目標(biāo)。

5、測試與認(rèn)證,保障商用上線。

和日常軟件開發(fā)一樣,需要對其進(jìn)行功能、性能、長穩(wěn)等層層測試,以確保能達(dá)到商用標(biāo)準(zhǔn)。此外也可以拿軟件和系統(tǒng)到鯤鵬上做鯤鵬展翅認(rèn)證,其可以擴展應(yīng)用的軟件使用空間并能夠加入鯤鵬生態(tài)。

C/C++代碼編譯原理及構(gòu)建流程

編譯型語言,從源碼到可執(zhí)行程序的歷程

C、C++、GO都是非常典型的編譯型語言,編譯型語言所開發(fā)的程序從x86平臺移植到鯤鵬平臺時一般都需要重新編譯才能運行,這點上文也已經(jīng)提到了。那么為什么需要重新編譯才能運行呢?接下來舉個簡單的例子。

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

上圖為test.c的源碼文件,首先經(jīng)過預(yù)處理,把代碼里面以#號開頭的代碼片斷編譯為預(yù)處理文件,預(yù)處理文件再經(jīng)由編譯生成匯編代碼。匯編代碼經(jīng)過匯編器生成目標(biāo)文件,這也就是常說的機器碼。然而機器碼是無法直接運行的,所以需要聯(lián)接動態(tài)庫或者靜態(tài)庫來最終生成可執(zhí)行文件。

編譯構(gòu)建的過程

從代碼工程的角度看,其可以分為兩類:一類是編譯構(gòu)建的腳本,二是源碼。在編譯構(gòu)建的腳本中一般會存在Makefile、cmakelist等一系列腳本文件,C和C++的源碼一般是有src、tests等文件。

那么編譯構(gòu)建腳本類文件在遷移過程中會涉及哪些因素?一般會涉及到編譯選項的移植,源碼類文件會涉及到編譯宏,另外可能還會有編譯器自帶的Builtin函數(shù)的移植、SSE intrinsic函數(shù)移植等。需要強調(diào)的是這里提到的是遷移過程中可能會涉及到的移植項,但在實際遷移過程中可能只需要編譯選項或修改編譯宏就可以。

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

C/C++代碼編譯構(gòu)建過程

首先要從獲取源碼開始,可以通過GitHub等開源社區(qū)來獲取;其次需要選擇所需的編譯環(huán)境,就是安裝編譯器gcc等;之后根據(jù)源碼的編譯腳本生成Makefile文件,再用Makefile編譯生成可持續(xù)文件。如果這部分代碼之中有依賴x86平臺的SO庫,那么這部分的依賴庫是需要重新編譯替換的。在編譯完成之后進(jìn)行安裝部署,之后進(jìn)入到實際的系統(tǒng)之中進(jìn)行測試。

典型的移植類問題

在對編譯構(gòu)建的流程有基本理解后,就需要深入了解實際遷移過程中所涉及到的各種移植項。

1.編譯腳本和編譯選項的移植

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

以上圖為例,其中x86下-m64代碼的主要功能是將應(yīng)用程序編譯為64位,對應(yīng)到鯤鵬上是用-mabi=lp64的編譯選項。上文有提到這編譯選項需要在腳本中修改,對應(yīng)的Cmakelists里有可能存在add_defin等多種定義方式。

再看常用的數(shù)據(jù)類型移植,眾所周知x86平臺上默認(rèn)的char類型是一種有符號的類型,對應(yīng)到鯤鵬上則是無符號類型。因此在移植過程中需要顯示定義并將char類型定義為有符號。一種方法是在源代碼里加上signed char,但是缺點是可能改不全從而引發(fā)一些不可預(yù)知的問題。另一種方法是直接用fsigned-char來修改,在不同架構(gòu)下差異化的編譯選項也可以通過gcc文檔進(jìn)行查詢。

2.編譯宏的移植

如果有相同的代碼片斷在不同平臺下可能會存在不同分支,需要將不同架構(gòu)下的性能優(yōu)勢發(fā)揮到較高水平。但是對于編譯器來說,如何才能得知要編譯哪些分支代碼呢?這就是編譯宏的作用。

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

如果大家對大型開源軟件有接觸過,相信對上圖這些編譯選項都不會陌生。gcc編譯器所自帶的x86編譯選項就是x86_64,對應(yīng)到鯤鵬平臺上是ark-64。當(dāng)然除了gcc編譯器自帶的自定義宏,在實際場景中也需要對自研代碼進(jìn)行自定義,在鯤鵬上也是需要進(jìn)行自定義替換。

不過針對編譯宏的替換只是其中一個方面,其最重要的是對編譯宏下面的代碼實現(xiàn)移植。x86代碼上有些編譯器自帶自定義宏,比如smd屬性相關(guān)的宏在x86上是SSE開頭的宏,對應(yīng)到鯤鵬平臺上就需要自定義它的編譯宏和所相對應(yīng)的分支。

3.Builtin函數(shù)移植

Builtin函數(shù)是編譯器自帶的函數(shù),其在實際遷移項目中相當(dāng)常見,主要是crc32校驗值的計算。需要移植的普通builtin函數(shù)實際并不多,大部分需移植的builtin函數(shù)集中在SSE intrinsic函數(shù)內(nèi)。

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

通過上圖可以看到在x86平臺上其和在鯤鵬平臺上是類似的,從命名來看有差異的地方就只存在于架構(gòu)。

4.內(nèi)聯(lián)匯編函數(shù)的移植

內(nèi)聯(lián)匯編對于部分開發(fā)者來說平時接觸的會比較少,所以又可能會感覺到陌生。

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

上圖列舉了將字節(jié)序進(jìn)行反序的例子,比如0X56781314反序輸出的是0X14137856,x86上對應(yīng)的是bswap指令,鯤鵬對應(yīng)的是rev指令,其它有些操作和寄存器都是基于內(nèi)聯(lián)匯編的語法規(guī)則進(jìn)行替換的。上圖的另一個例子是Builtin函數(shù),列舉了內(nèi)聯(lián)匯編轉(zhuǎn)換用鯤鵬上面的Builtin函數(shù)做替換的例子。比如popcount是對二進(jìn)制數(shù)里面的1進(jìn)行計數(shù),對應(yīng)到鯤鵬平臺上所替換的是popcountll。

5.SSE intrinsic函數(shù)移植(SIMD技術(shù))

關(guān)于SSE intrinsic函數(shù)的移植,在這之前需要先了解SIMD的技術(shù)。SIMD(Single Instruction Multi Data)是一種單指令處理多數(shù)據(jù)流的并行處理技術(shù),能夠在批量數(shù)據(jù)操作時進(jìn)行向量 化運算加速,具有較高的執(zhí)行效率,在多媒體處理、矩陣運算等場景都有廣泛的應(yīng)用。

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

Intel的SIMD擴展指令統(tǒng)稱SSE,主要分為三類,MMX是64位寄存器,SSE到SSE4是28位的,三是AVX256和AVX512。鯤鵬基于SIMD的技術(shù)發(fā)展比較成熟,現(xiàn)在有些基于開源量的NEON庫主要是在圖象處理和視頻處理層面。

6.SSE intrinsic函數(shù)移植(MMX/SSE)

經(jīng)過調(diào)用編譯器就能夠基于C函數(shù)調(diào)用完成對SIMD技術(shù)的應(yīng)用,極大方便了開發(fā)者。

從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論

以上圖為例,在x86上用的是-m64的向量,add是關(guān)鍵字,而且這是加法運算,后面32是數(shù)據(jù)類型。對應(yīng)到鯤鵬上是int32×2然后再做加法運算,這常用的C函數(shù)規(guī)則是類似的。針對SSE指令,從內(nèi)存中加載4個單精度浮點數(shù)據(jù)到寄存器,x86是load,對應(yīng)到鯤鵬用的是vld1q。

7.SSE intrinsic函數(shù)移植(AVX)

AVX指令和MMX類似,只不過其位數(shù)不同。以AVX指令使用了256位寄存器運算為例,向量A和向量B中分別存儲了8個單 精度浮點型(32位)。該指令將向量A和向量B中的8個數(shù)值分別相加,并將結(jié)果以返回值的形式返回 (結(jié)果中依然是8個單精度浮點型數(shù)據(jù)) ,最后再從向量寄存器中分別取出8個單精度浮點數(shù)累加得到結(jié)果。

對應(yīng)到鯤鵬方面,鯤鵬處理器采用精簡指令集,使用128位寄存器實現(xiàn)SIMD(Single Instruction Multi Data)計算。在實現(xiàn)本例16個浮點數(shù)的相加時,通過兩條vaddq_f32指令來分別完成,每條指令完成兩組共8個浮點數(shù)算,最后再從向量寄存器中分別取出8個浮點數(shù)累加。

寫在最后

對于開發(fā)者而言,代碼和軟件遷移是一套必須要掌握的技能。尤其是各種智能終端數(shù)量暴漲,物聯(lián)網(wǎng)飛速發(fā)展的當(dāng)下,x86平臺已經(jīng)難以適應(yīng)全生態(tài)的發(fā)展,從x86遷移至ARM平臺,正是現(xiàn)在的大勢所趨。而鯤鵬生態(tài),則為每一位有遷移需求的開發(fā)者提供了最便利的工具和環(huán)境。沒有哪一款平臺是最好的,只有最適合業(yè)務(wù)的那款平臺,從x86到ARM,答案正在逐漸清晰。

極客網(wǎng)企業(yè)會員

免責(zé)聲明:本網(wǎng)站內(nèi)容主要來自原創(chuàng)、合作伙伴供稿和第三方自媒體作者投稿,凡在本網(wǎng)站出現(xiàn)的信息,均僅供參考。本網(wǎng)站將盡力確保所提供信息的準(zhǔn)確性及可靠性,但不保證有關(guān)資料的準(zhǔn)確性及可靠性,讀者在使用前請進(jìn)一步核實,并對任何自主決定的行為負(fù)責(zé)。本網(wǎng)站對有關(guān)資料所引致的錯誤、不確或遺漏,概不負(fù)任何法律責(zé)任。任何單位或個人認(rèn)為本網(wǎng)站中的網(wǎng)頁或鏈接內(nèi)容可能涉嫌侵犯其知識產(chǎn)權(quán)或存在不實內(nèi)容時,應(yīng)及時向本網(wǎng)站提出書面權(quán)利通知或不實情況說明,并提供身份證明、權(quán)屬證明及詳細(xì)侵權(quán)或不實情況證明。本網(wǎng)站在收到上述法律文件后,將會依法盡快聯(lián)系相關(guān)文章源頭核實,溝通刪除相關(guān)內(nèi)容或斷開相關(guān)鏈接。

2020-05-20
從x86到ARM,C和C++實現(xiàn)90%代碼自動遷移的方法論
x86與ARM之爭,已經(jīng)貫穿了很長時間,過去一直是x86架構(gòu)比較受到市場和開發(fā)者的歡迎。

長按掃碼 閱讀全文