ARM服務器代碼移植經驗和總結

王博 架構師技術聯盟

ARM服務器代碼移植經驗和總結


經歷過2個項目的業務代碼從X86服務器遷移到aarch64泰山服務器上,以前沒有相關經驗摸索了好久,踩了很多坑,現在遷移工作也差不多收尾了,Taishan服務器上跑比X86的溜多了,筆者寫了一篇代碼遷移經驗總結,歡迎大家參考。

編程語言簡介

按照翻譯方式的不同,高級語言通常可以分為兩類:一類是編譯翻譯,一類是解釋翻譯,分別對應著編譯型語言和解釋型語言。

1.編譯型語言

典型的如C、C++語言,都屬於編譯型語言,源代碼到執行的過程概括如圖1-1所示。C/C++編譯好的程序是機器指令,由操作系統加載到存儲器(一般為內存)後由CPU直接執行。

ARM服務器代碼移植經驗和總結


圖 編譯型語言執行過程

基於編譯型語言開發的應用程序,例如C/C++語言應用程序,其編譯後得到可執行程序,可執行程序執行時依賴的指令是CPU架構相關的。因此,基於x86架構編譯的C/C++語言應用程序,無法直接在TaiShan服務器運行,需要進行移植編譯,移植編譯過程中遇到的問題可以參考第2、3章提供的方法解決。

2.解釋型語言

典型的如Java、Python語言,都屬於解釋型語言,源代碼到執行的過程概括如圖1-2所示。Java/Python編譯好的程序是平臺無關的字節碼,由虛擬機解釋執行,虛擬機完成平臺差異的屏蔽。

ARM服務器代碼移植經驗和總結


圖 解釋型語言執行過程

基於解釋型語言開發的應用程序,是CPU架構不相關的,例如Java、Python,將這類應用程序移植到TaiShan服務器,無需修改和重新編譯,按照與x86一致的方式部署和運行應用程序即可。

Java應用程序jar包內,可能包含基於C/C++語言開發的so庫文件,這類so庫需要移植編譯,移植編譯so庫遇到的問題可以參考第2、3章提供的方法解決,使用編譯得到的so庫重新打包jar包。

準備工作

C/C++程序移植需要安裝編譯器,推薦使用gcc7.3及以上版本(最低不低於4.8.5),下載安裝參考鏈接:

  • gcc7.3版本下載地址:
  • http://ftp.gnu.org/gnu/gcc/gcc-7.3.0/
  • 安裝步驟參考:https://gcc.gnu.org/install/


移植相關問題處理:編譯腳本移植類問題

1.1 -m64編譯選項

現象描述

告警信息:gcc:error: unrecognized command line option ‘-m64’

可能原因

-m64是x86 64位應用編譯選項,m64選項設置int為32bits及long、指針為64 bits,為AMD的x86 64架構生成代碼。在ARM64平臺無法支持。

處理步驟

將ARM64平臺對應的編譯選項設置為-mabi=lp64。

1.2 char數據類型的符號

現象描述

告警信息:warning:comparison is always false due to limitedrange of data type

可能原因

char變量在不同CPU架構下默認符號不一致,在x86架構下為signed char,在ARM64平臺為unsigned char,移植時需要指定char變量為signed char。

處理步驟

在編譯選項中加入“-fsigned-char”選項,指定ARM64平臺下的char為有符號數。

源碼修改類問題

2.1 代碼中彙編指令需要重寫

現象描述

ARM的彙編語言與x86完全不同,需要重寫,涉及使用嵌入彙編的代碼,都需要針對ARM進行配套修改。

處理步驟

需要重新實現彙編代碼段。

示例:

在x86架構下:

ARM服務器代碼移植經驗和總結


在ARM64平臺下,使用gcc內置函數實現:

ARM服務器代碼移植經驗和總結


2.2 替換x86 CRC32彙編指令

現象描述

編譯錯誤:unknownmnemonic `crc32q\\\\\\' -- `crc32q (x3),x2\\\\\\'或operand 1 should be an integer register -- `crc32b (x1),x0\\\\\\'

或unrecognizedcommand line option ‘-msse4.2’。

可能原因

x86使用的是crc32b和crc32q彙編指令完成CRC32C校驗值計算功能,而ARM64平臺使用crc32cb、crc32ch、crc32cw、crc32cx 4個彙編指令完成CRC32C校驗值計算功能。

處理步驟

請使用crc32cb、crc32ch、crc32cw、crc32cx取代x86的CRC32系列彙編指令,替換方法如表所示,並在編譯時添加編譯參數-mcpu=generic+crc。

ARM服務器代碼移植經驗和總結


示例:

在x86下的實現:

ARM服務器代碼移植經驗和總結


在ARM64平臺下的實現:

ARM服務器代碼移植經驗和總結


2.3 替換x86 bswap彙編指令

現象描述

編譯報錯:Error:unknown mnemonic `bswap\\\\\\' -- `bswap x3\\\\\\'。

可能原因

bswap是x86的字節序反序指令,需替換為ARM64的rev指令。

處理步驟

x86指令實現的bswap如下:

ARM服務器代碼移植經驗和總結


替換為ARM64指令後如下:

ARM服務器代碼移植經驗和總結


2.4 替換x86 rep彙編指令

現象描述

編譯報錯:unknownmnemonic `rep` -- `rep`。

可能原因

rep為x86的重複執行指令,需替換為ARM64的rept指令。

處理步驟

替換方法如下:

替換前:

ARM服務器代碼移植經驗和總結


替換後:

ARM服務器代碼移植經驗和總結


2.5 快速移植內聯SSE/SSE2應用

現象描述

部分應用採用了gcc封裝的用SSE/SSE2實現的函數,但是gcc目前沒有提供對應的ARM64平臺版本,需要實現對應函數。

處理步驟

目前已有開源代碼實現了部分ARM64平臺的函數,代碼下載地址:https://github.com/open-estuary/sse2neon.git

使用方法如下:

步驟 1 將下載項目中的SSE2NEON.h文件拷貝到待移植項目中。

步驟 2 在源文件中刪除如下代碼。

ARM服務器代碼移植經驗和總結


步驟 3 在源代碼中包含頭文件SSE2NEON.h

2.6 弱內存序導致程序執行結果和預期不一致

現象描述

弱內存序導致程序執行結果和預期不一致。

可能原因

ARM64平臺是弱內存序,原理如下:

1. 同一份數據,在cache裡面存在多份,需要CPU之間進行同步。

ARM服務器代碼移植經驗和總結


2. 代碼編寫順序和執行順序可能不一樣。

ARM服務器代碼移植經驗和總結


CPU內部是流水線執行,在執行到x=1時,如果x在內存,那麼CPU就會等待x導入到cache,在等待的過程中如果y已經在cache中了,那麼CPU會執行y=1,這樣就導致後面的語句先執行。

對系統的影響

· 影響無鎖編程的代碼。

· 對於使用信號量機制寫的互斥代碼,因為信號量函數已經帶了內存屏障的指令,所以無影響。

處理步驟

找到使用無鎖編程的代碼,檢查是否用內存屏障指令保證了數據的一致性。

使用內存屏障指令保證對共享數據的訪問和預期一致。

示例:

ARM服務器代碼移植經驗和總結


2.7 對結構體中的變量進行原子操作時程序異常coredump

現象描述

程序調用原子操作函數對結構體中的變量進行原子操作,程序coredump,堆棧如下:

ARM服務器代碼移植經驗和總結


可能原因

ARM64平臺對變量的原子操作、鎖操作等用到了ldaxr、stlxr等指令,這些指令要求變量地址必須按變量長度對齊,否則執行指令會觸發異常,導致程序coredump。

一般是因為代碼中對結構體進行強制字節對齊,導致變量地址不在對齊位置上,對這些變量進行原子操作、鎖操作等會觸發問題。

處理步驟

代碼中搜索“#pragmapack”關鍵字(該宏改變了編譯器默認的對齊方式),找到使用了字節對齊的結構體,如果結構體中變量會被作為原子操作、自旋鎖、互斥鎖、信號量、讀寫鎖的輸入參數,則需要修改代碼保證這些變量按變量長度對齊。

2.8 核數目硬編碼

TaiShan服務器相對於x86服務器,CPU核數會有變化,如果模塊代碼針對處理器core數目硬編碼,則會造成無法充分利用系統能力的情況,例如CPU核的利用率差異大或者綁核出現跨numa的情況。

處理步驟

您可以通過搜索代碼中的綁核接口(sched_setaffinity)來排查綁核的實現是否存在CPU核數硬編碼的情況。

如果存在,則根據TaiShan服務器實際核數進行修改,消除硬編碼,可通過接口(sysconf(_SC_NPROCESSORS_CONF))來獲取實際核數再進行綁核。

2.9 雙精度浮點型轉整型時數據溢出,與X86平臺表現不一致

現象描述

C/C++雙精度浮點型數轉整型數據時,如果超出了整型的取值範圍,TaiShan平臺的表現與x86平臺的表現不同。

ARM服務器代碼移植經驗和總結


可能原因

在兩個平臺下,是兩套CPU架構,其中的算數邏輯單元的實現可能會有差異,操作系統、編譯器的實現都會有所不同。x86(指令集)中的浮點到整型的轉換指令,定義了一個indefinite integer value——“不確定數值”(64bit:0x8000000000000000),大多數情況下x86平臺確實都在遵循這個原則,但是在從double向無符號整型轉換時,又出現了不同的結果。鯤鵬的處理則非常清晰和簡單,在上溢出或下溢出時,保留整型能表示的最大值或最小值,開發者並不會面對不確定或無法預期的結果。

處理步驟

參考如下數據轉換的表格,調整代碼中的實現:

double數據向long轉換:

ARM服務器代碼移植經驗和總結


double數據向unsigned long轉換:

ARM服務器代碼移植經驗和總結


double數據向int轉換:

ARM服務器代碼移植經驗和總結


double數據向unsigned int轉換:

ARM服務器代碼移植經驗和總結


編譯優化項

4.1 gcc編譯器優化浮點運算精度

現象描述

編譯優化選項設置-O2級別及以上時,相同的浮點數乘加運算在x86平臺和ARM64平臺的運算結果,在小數點後16位存在差異。

可能原因

ARM64平臺編譯優化選項設置為-O2級別及以上,進行浮點數的乘加運算(a+=b*c),運算結果的精度只能精確到小數點後16位。在配置-O2選項時,gcc使用融合指令fmadd完成乘加運算,而不是fadd和fmul。

fmadd將浮點數的乘法和加法看成不可分的一個操作,不對中間結果進行舍入,從而導致計算結果有所差別。

對系統的影響

編譯優化選項設置-O2級別及以上時,浮點乘加運算的性能有提升,但是運算的精度受到影響。

處理步驟

添加編譯選項-ffp-contract=off可以關閉該優化。

4.2 增加編譯選項匹配Kunpeng處理器架構,提升性能

在編譯時增加編譯選項指定處理器架構為armv8,使編譯器按照Kunpeng處理器的架構和微架構生成可執行程序,提升性能。

處理步驟:

編譯選項中添加-march=armv8-a。

4.3 增加編譯選項匹配Kunpeng處理器流水線,提升性能

如果使用了gcc 9.1以上的版本,在編譯時增加編譯選項指定使用tsv110流水線,使編譯器按照Kunpeng處理器的流水線編排指令執行順序,充分利用流水線的指令集並行,提升性能。

處理步驟:

編譯選項中添加 -mtune=tsv110。

ARM服務器代碼移植經驗和總結


☆ 新年特惠福利 ☆

全店鋪技術資料打包(全)》電子書,總價值

240元,新年特惠活動期間178元。

全店鋪技術資料打包(全) 目錄如下(32本):

  • 《RDMA原理分析、對比和技術實現解析》
  • 《數據備份和副本管理技術全面解析》
  • 《容器技術架構、網絡和生態詳解》
  • 《閃存技術、產品和發展趨勢全面解析》
  • 《虛擬化技術最詳細解析》
  • 《傳統企業存儲知識完全解析》
  • 《IO知識和系統性能深度調優全解》
  • 《業界主流數據中心存儲雙活完全解析》
  • 《Ceph技術架構、生態和特性詳細對比分析》
  • 《數據中心大二層交換技術詳解》
  • 《VMware雲數據中心(私有云)解決方案詳解》
  • 《大數據時代數據重刪技術詳解》
  • 《高性能計算HPC技術、方案和行業全面解析》
  • 《Kubernetes技術和實戰總結》
  • 《詳解DPDK和SPDK技術知識點》
  • 《InfiniBand架構和技術實戰總結》
  • 《雲計算PaaS主流軟件、產品和廠商分析》
  • 《Cloud Foundry技術架構詳解分析》
  • 《OpenStack技術和實戰詳解》
  • 《詳解SCM/NVM技術現狀和研究方向》
  • 《NVMe基礎架構和概念詳解》
  • 《NVMe技術標準和原理深度解析》
  • 《VMWare NSX網絡技術深度解析》
  • 《數據中心服務器知識完全解析》
  • 《架構師技術聯盟原創資料8本》

”全店鋪技術資料打包(全)” 電子書詳情目錄和內容請通過“閱讀原文”獲取。

溫馨提示:請識別二維碼關注公眾號,點擊原文鏈接獲取架構師技術全店資料打包彙總(全)”電子書資料詳情。



分享到:


相關文章: