關於cpu體系架構的一些有趣的故事分享

從排查一次匪夷所思的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參數的功能。