從排查一次匪夷所思的coredump,引出各種體系架構的差異,
本文中的所有內容來自學習DCC888的學習筆記或者自己理解的整理,如需轉載請注明出處,周榮華@燧原科技
1 背景
從全世界有記載的第一臺計算機Z1 (computer) - Wikipedia在1936年發明,到1946年馮諾依曼體系架構的清晰提出,計算機體系架構的演進雖然沒有什么革命性的變化,但各種體系架構的微調還是很明顯的,
發展到現在雖然存在X86/ARM/MIPS/ALPHA/PPC/RISC-V等多種門派,但實際的設計思想上,主要有兩種,一種是基于X86的系統架構,另外一種就是其他系統架構,
為什么這么分?
因為X86的很多特性,基本上只有X86有,而其他體系架構基本上都是共享的另外一種,
例如CISC和RISC,位元組對齊,變長指令和固定長度指令,指令尋址模式,等等,
現在用的各種體系架構,只有x86是復雜指令集,變長,記憶體訪問可以不是位元組對齊的(當然,對齊之后性能更好),沒有固定的加載和保存指令,而是采用很多計算指令直接訪問記憶體,
相對于x86,其他體系架構,包括ARM/MIPS/ALPHA/PPC/RISC-V,都是精簡指令集,指令長度也是固定的,記憶體訪問必須對齊,否則coredump,記憶體的訪問只能通過有限的幾個加載和保存指令進行,其他計算指令僅限于在暫存器上操作,
2 體系架構
計算機的體系架構,英文稱為Computer architecture - Wikipedia,涉及的作業主要分三部分:
指令集、微架構和系統設計,
其中指令集相當于用戶界面,是軟體和硬體的介面,
微架構是指令集的具體實作,
系統設計主要是支撐微架構的記憶體、總線、功耗等設計,
下面的問題單就X86來闡述,
32位的處理器太古老,我們單說64位之后的故事,
x86-64 - Wikipedia講述了x86-64的體系架構的微架構演程序序:

最早出來的是x86-64,相當于64位x86的基線版本,基本上所有64位x86處理器都支持,包括常見的MMX、SSE、FPU,都不是問題,基于這個基線版本往上發展出了v2/v3和v4版本,
現在虛擬機(QEMU)基本上支持到v2就終結了,所以后面v3/v4變成了少數用戶的選擇,隨著這些微架構的演進,不僅指令集,暫存器也會有較大變化,那怎么保證編譯出來的程式在各種x86的硬體上都能正常運行是個大問題,解決這個問題的主角就是編譯器,
考慮到泛化和性能的不同要求,即使在同樣的體系架構下,也可以指定具體的硬體版本,這就是gcc/clang等編譯器的arch引數的由來,
x86 Options (Using the GNU Compiler Collection (GCC))中提到的arch的取值從各種具體的處理器型號,到泛化的v2/v3/v4,都是為了方便程式員可以盡可能保證兼容性的前提下,也能提升性能,
如果不考慮泛化,用戶還可以簡單用一個-march-native在x86平臺上實作基于當前硬體的極致優化,
3 問題
這里碰到的一個問題就是極致優化帶來的兼容性問題,
某服務器上編譯出來的版本,在部分x86的機器上能正常運行,但部分x86機器上不能正常運行,通過gdb斷點排查,報非法指令,而且代碼段指向vxorps這條指令,后面緊跟著的3個暫存器非常扎眼zmm,

zmm暫存器是v4版本引入的功能,
能運行含zmm暫存器指令的cpu是“Intel(R) Xeon(R) Gold 6130 CPU @ 2.10GHz”,網上查了一下,是intel 2017年的產品,
到目前位置,MMX指令使用的暫存器經過了三代演進,xmm/ymm/zmm:
xmm0 ~ xmm15, are 128 bits, almost every modern machine has it, they are released in 1999.
ymm0 ~ ymm15, are 256 bits, new machine usually have it, they are released in 2011.
zmm0 ~ zmm31, are 512 bits, normal pc probably don't have it (as the year 2016),
由于后一代的暫存器長度是上一代的兩倍,決定了前一代處理器是無法使用后一代處理器的暫存器的,相反,本地如果是更高一級的暫存器,可以運行低級的暫存器相關指令,
同樣的代碼,都指定-march=native的情況下,在“AMD Ryzen Threadripper 3960X 24-Core Processor”上編譯的結果是這樣的,指令本身沒有變,暫存器從zmm變成了xmm,

4 問題的解決
既然知道是gcc的arch指定有問題導致的,就要從修改arch入手,
做了一些實驗,例如下面左邊是-march=native編譯,右邊是-march=x86-64的結果,可以看出native編譯出來使用incl,相對于addl,使指令更短,性能更好,

最終各種實驗對比結果看結論如下:
-m64 -march=x86-64 -mtune=generic 編譯出來的結果使用xmm暫存器
-march=native 編譯出來的結果,在amd服務器上是xmm暫存器,在intel服務器上是zmm暫存器
為了保證兼容性,先統一用-m64 -march=x86-64 -mtune=generic 進行編譯,
5 怎么做的更好
由于大多數編譯器還不支持-march=x86-64-v2等直接選擇x86-64具體版本的選項,有一種折中方案是native-avx512的做法,一般引數是這樣的:
add_compile_options (-march=native)
add_compile_options (-mno-avx512f)
這樣寫的意思是其他方面可以盡量用本地能支持的最新的,但不要使用avx512f的功能,約等于x86-64-v3這個arch引數的功能,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/498998.html
標籤:其他
上一篇:編程模式-表驅動編程
下一篇:單機高并發模型設計
