朱 強(qiáng)
(中國(guó)航空工業(yè)集團(tuán)公司洛陽(yáng)電光設(shè)備研究所,河南 洛陽(yáng) 471000)
在型號(hào)調(diào)試過(guò)程中,遇到程序的執(zhí)行順序與CPU實(shí)際執(zhí)行的順序不完全一致的問(wèn)題。經(jīng)過(guò)對(duì)此問(wèn)題的進(jìn)一步研究,發(fā)現(xiàn)現(xiàn)代處理器和編譯器會(huì)對(duì)代碼的執(zhí)行順序進(jìn)行一定的調(diào)整和優(yōu)化。本文通過(guò)對(duì)處理器的架構(gòu)、流水線執(zhí)行方式以及編譯器的優(yōu)化原則等內(nèi)容進(jìn)行研究,經(jīng)過(guò)查閱相關(guān)手冊(cè),通過(guò)在代碼中嵌入同步指令sync以及volatile關(guān)鍵字可以保證代碼嚴(yán)格按照順序執(zhí)行[1]。
在進(jìn)行型號(hào)調(diào)試時(shí),某總線時(shí)序要求先對(duì)地址A進(jìn)行寫(xiě)操作,然后再對(duì)地址B和C分別進(jìn)行一次讀操作。即需要嚴(yán)格按照如下三行代碼順序執(zhí)行:
由于CPU先執(zhí)行了讀操作,而后執(zhí)行了寫(xiě)操作,與預(yù)期的執(zhí)行順序不一致,造成總線時(shí)序錯(cuò)誤。
當(dāng)前,為了提高CPU處理器的處理速度、指令執(zhí)行的并行度,大多數(shù)CPU都采用多級(jí)流水線、亂序執(zhí)行、分支預(yù)測(cè)等技術(shù)。這些技術(shù)的應(yīng)用極大提高了處理能力。
在以前處理器設(shè)計(jì)中,處理器在執(zhí)行代碼時(shí),按照編譯的匯編語(yǔ)言代碼的順序進(jìn)行執(zhí)行,這樣的設(shè)計(jì)稱為按序執(zhí)行。
PowerPC E500核采用七級(jí)流水線技術(shù),分別是取指令1、取指令2、指令譯碼、指令分發(fā)、指令執(zhí)行、指令完成、結(jié)果寫(xiě)回。
不同的指令的執(zhí)行周期不同,在e500核中,比如跳轉(zhuǎn)指令單元(Branch Unit)、簡(jiǎn)單運(yùn)算單元(Simple Unit)的指令可以在一個(gè)周期即可完成,而多指令單元(Multiple-cycle IU)則需要4、11甚至35個(gè)周期完成。數(shù)據(jù)加載存儲(chǔ)指令單元(Load/store Unit)執(zhí)行一般需要3個(gè)周期。如圖1所示。
圖1 PowerPC架構(gòu)7級(jí)流水線架構(gòu)示意圖
隨著處理性能要求的提高,在設(shè)計(jì)處理器時(shí)為了提高運(yùn)行速度,一般會(huì)采用亂序執(zhí)行技術(shù)。亂序執(zhí)行技術(shù)其本質(zhì)是違背了源代碼按照順序執(zhí)行的原則,但是能夠保證最終的運(yùn)算結(jié)果與預(yù)期結(jié)果是一致的。同時(shí),處理器還設(shè)計(jì)了多級(jí)高速緩存機(jī)制,如果在使用過(guò)程中,沒(méi)有采取相應(yīng)的措施,處理器最終的運(yùn)算結(jié)果與我們預(yù)期的結(jié)果不同。
處理器在CACHE中取出指令,會(huì)進(jìn)行相應(yīng)的分析,找出相互獨(dú)立的指令,并將這些相互獨(dú)立的指令送到不同的邏輯單元中執(zhí)行,這樣提高了執(zhí)行效率。而對(duì)于有相互依賴的指令,則按照順序分別執(zhí)行。
在PowerPC架構(gòu)處理器中,官方文檔PowerPC?e500 Core Family Reference Manual中提到,在e500核處理器中采用超標(biāo)量7級(jí)流水技術(shù),即一個(gè)時(shí)鐘周期可以解析兩條指令和執(zhí)行完兩條指令,指令的完成是按照順序的,指令是并行執(zhí)行,但是執(zhí)行是可以是無(wú)序的。
需要說(shuō)明的是,如果語(yǔ)句之間有依賴關(guān)系或者同一個(gè)時(shí)鐘周期能夠更新兩個(gè)以上的寄存器時(shí),則CPU不會(huì)對(duì)代碼的執(zhí)行順序進(jìn)行調(diào)整,官方文檔對(duì)此做了說(shuō)明:
在前言的案例中,如果將代碼改成:
則CPU執(zhí)行的順序也會(huì)嚴(yán)格按照上述代碼執(zhí)行,這是因?yàn)榍皟删涠际菍?duì)地址0xef000000進(jìn)行寫(xiě)操作然后再進(jìn)行讀操作。CPU會(huì)認(rèn)為這兩句是有依賴的,因此嚴(yán)格按照代碼順序執(zhí)行。
處理器為了提高同時(shí)執(zhí)行指令的效率,一般會(huì)將分支條件里的指令同時(shí)取出,同時(shí)執(zhí)行,等到分支條件結(jié)果計(jì)算完成后,再將錯(cuò)誤的結(jié)果舍棄,這樣可以避免多次跳轉(zhuǎn)。
上面例子中,如果a不計(jì)算出來(lái),t是無(wú)法繼續(xù)計(jì)算的。但是實(shí)際上處理器會(huì)將三個(gè)計(jì)算同時(shí)執(zhí)行,當(dāng)a的值計(jì)算后再將不滿足條件的結(jié)果舍棄。
在PowerPC架構(gòu)處理器中同樣具備分支探測(cè)和預(yù)測(cè)功能,但是處理器真正的分支執(zhí)行是不可預(yù)測(cè)的,但是能夠保證結(jié)果的正確性。
在某些場(chǎng)合下,要求CPU的代碼執(zhí)行嚴(yán)格按照匯編代碼順序執(zhí)行,例如對(duì)某些硬件寄存器的讀寫(xiě)操作,有嚴(yán)格的時(shí)序要求。如果CPU還是按照亂序執(zhí)行,則會(huì)出現(xiàn)指令執(zhí)行與匯編代碼順序不同步問(wèn)題。
不僅處理器在設(shè)計(jì)時(shí)考慮亂序執(zhí)行的情況,編譯器同樣進(jìn)行亂序優(yōu)化。相比處理器亂序優(yōu)化,編譯器亂序執(zhí)行優(yōu)化更有優(yōu)勢(shì),因?yàn)榫幾g器可以在很大范圍內(nèi)進(jìn)行源代碼的分析,而處理器則只能分析小部分指令,這樣使得編譯器能夠做出更優(yōu)的決策。
處理器的預(yù)取單元容量和能力有限,每次分析的指令并發(fā)范圍較小,但是編譯器能夠?qū)Υ蠓秶拇a進(jìn)行整體分析,能夠分析出更多的可以并發(fā)的指令,并根據(jù)處理器特點(diǎn),對(duì)指令進(jìn)行重排,使得處理器更容易預(yù)取和并發(fā)執(zhí)行,有利于提高處理器的亂序并發(fā)執(zhí)行性能。
因此,在現(xiàn)代的編譯器中一般都具備指令亂序優(yōu)化功能,同時(shí)根據(jù)指令對(duì)存儲(chǔ)器的訪問(wèn)情況,對(duì)指令進(jìn)行進(jìn)一步優(yōu)化,減少對(duì)存儲(chǔ)器的訪問(wèn),盡量控制在內(nèi)部寄存器和CACHE中,提高運(yùn)行速度。另外,編譯器也會(huì)根據(jù)指令情況,提高CACHE的命中率,因此編譯器如果開(kāi)啟了優(yōu)化選項(xiàng),實(shí)際生成的匯編代碼可能與源代碼的執(zhí)行順序不一致。
但是,不管是處理器還是編譯器的亂序執(zhí)行,都不應(yīng)該改變最終的執(zhí)行結(jié)果的正確性,也就是as-ifserial語(yǔ)義。在這種語(yǔ)義的要求下,這就要求對(duì)于有依賴的指令或者數(shù)據(jù)有上下文要求的操作不能改變順序。因此,在編寫(xiě)單線程代碼時(shí),實(shí)際執(zhí)行的結(jié)果是符合預(yù)期的。
GCC編譯器有多個(gè)優(yōu)化選型,這些選項(xiàng)可以設(shè)置,編譯器優(yōu)化的目的是生成的代碼執(zhí)行時(shí)間盡量短,代碼占用的空間也應(yīng)當(dāng)盡量小。
編譯器優(yōu)化過(guò)程如下:編譯器讀取設(shè)置的優(yōu)化參數(shù),然后通過(guò)語(yǔ)法分析器對(duì)源代碼進(jìn)行翻譯,并抽象成語(yǔ)法樹(shù)。語(yǔ)法樹(shù)再經(jīng)過(guò)代碼生成器轉(zhuǎn)換為RTL,然后進(jìn)行優(yōu)化。最終得到優(yōu)化后的匯編代碼或者機(jī)器碼。
例如下面的代碼,主函數(shù)最后調(diào)用求和,在編譯器選項(xiàng)為優(yōu)化時(shí),編譯器直接將sum函數(shù)中的值在編譯時(shí)就求出,并將結(jié)果放到r3寄存器中,而沒(méi)有使用選擇優(yōu)化選項(xiàng)時(shí),則需要在CPU中執(zhí)行求和代碼。
圖2 編譯器優(yōu)化前和優(yōu)化后的代碼對(duì)比示意圖
在PowerPC架構(gòu)處理器中,官方文檔提供了相應(yīng)的指令,用于顯式強(qiáng)制按照匯編后的代碼順序執(zhí)行,主要包括isync(指令同步)、mbar(內(nèi)存屏障)、msync(同步指令)以及eieio等。
msync指令是要求其指令之前的語(yǔ)句必須執(zhí)行完成,才能取后續(xù)的指令。其中指令eieio是經(jīng)典PowerPC架構(gòu)的指令集,在book E版本以上中使用mbar指令代替eieio指令,同時(shí)也支持eieio指令。
對(duì)于一些不希望被優(yōu)化的指令,可以通過(guò)volatile關(guān)鍵字來(lái)抑制,這樣編譯器可以不對(duì)相關(guān)的變量進(jìn)行優(yōu)化。
經(jīng)過(guò)以上分析,為了防止編譯進(jìn)行代碼重排序和防止從緩沖中取數(shù)據(jù),需要在變量前加volatile進(jìn)行修飾。同時(shí)為了防止CPU執(zhí)行代碼時(shí)并行處理指令導(dǎo)致的亂序問(wèn)題,需要在代碼處增加同步指令。修改后代碼如下:
經(jīng)過(guò)測(cè)試,代碼執(zhí)行順序正常。
(1)處理器和編譯器為了提高執(zhí)行效率,會(huì)對(duì)代碼的執(zhí)行順序進(jìn)行重組,但是處理器和編譯器對(duì)整體的運(yùn)算結(jié)果是有保證的。
(2)只有在時(shí)序要求嚴(yán)格的情況下需要顯式調(diào)用同步指令,具體情況如下:①在操作某些硬件寄存器或者雙口存儲(chǔ)器時(shí),如果有明顯的讀寫(xiě)時(shí)序要求,則應(yīng)當(dāng)添加同步指令,且變量需要添加volatile進(jìn)行修飾。②中斷和主程序都使用同一個(gè)全局變量,在定義全局變量時(shí)增加volatile進(jìn)行修飾,同時(shí)主程序在進(jìn)行變量的寫(xiě)操作時(shí),需要關(guān)中斷,然后再開(kāi)中斷。③在使用多線程或者多任務(wù)時(shí)共享同一個(gè)全局變量,在定義全局變量時(shí)增加volatile進(jìn)行修飾,同時(shí)在進(jìn)行變量的寫(xiě)操作時(shí),也需要增加信號(hào)量進(jìn)行寫(xiě)保護(hù)。
(3)在沒(méi)有特殊要求的情況下,不建議使用編譯器優(yōu)化選項(xiàng)。