?? appendix_a.htm
字號:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0038)http://www.xtrj.org/smr/Appendix_A.htm -->
<HTML><HEAD><TITLE>附錄A 指令的時序和優(yōu)化 MIPS CPU高度流水化</TITLE><!-- http://www.xtrj.org/smr/Appendix_A.htm -->
<META content="MSHTML 6.00.2900.2180" name=GENERATOR>
<META content=FrontPage.Editor.Document name=ProgId>
<META http-equiv=Content-Type content="text/html; charset=gb2312"></HEAD>
<BODY>
<P>附錄A 指令的時序和優(yōu)化 <BR>MIPS
CPU高度流水化,所以它們執(zhí)行代碼的速度依賴于流水線的工作情況。有某些情況下,代碼的正確性依賴于流水線的工作方式---特別是使用CPU控制協(xié)處理器0的指令和寄存器。
<BR>通過顯式使用寄存器而傳遞的依賴性相當明顯,只不過比較凌亂。除此以外,在隱式使用的寄存器中也有一些偶然的依賴關系。例如狀態(tài)寄存器中的CPU控制標志會影響到所有指令的指令的執(zhí)行,改變這些標志必須非常小心。
<BR>大部分MIPS指令需要在流水線RD階段的結(jié)束時得到它們的操作數(shù),并且要在隨后的ALU階段的結(jié)束時產(chǎn)生這些指令的運行結(jié)果,這如圖A.1所示的那樣。如果所有的指令總能夠遵守這些規(guī)則,任何指令序列都能夠以最大速度正確的運行。在MIPS架構(gòu)中最大的奧妙就是絕大多數(shù)指令都能遵守這些規(guī)則。
<BR>在由于某些原因而不能做到的情況下,使用從前面的緊鄰指令處得到操作數(shù)的指令不能及時正確的運行。這種情況能被硬件檢測到,然后通過延遲第二條指令直到數(shù)據(jù)準備好以使之得到修正,或者它可以留給程序員來避免產(chǎn)生試圖使用未準備好的數(shù)據(jù)(pipeline
hazard流水線冒險)的指令序列。 <BR><BR>A.1 避免冒險:使代碼正確 <BR><BR>可能的冒險包括下面這些情況:
<BR>1.load延遲:在早期的MIPS
CPU中的這是一種流水線冒險;緊跟著load指令后面的指令不能引用由load產(chǎn)生的數(shù)據(jù)。有時候當沒有有用的東西能被安全的移到被延遲的指令槽時,需要編譯器/匯編器使用一條nop指令。但從R4000開始,MIPS
CPU已經(jīng)是互鎖的了,這樣冒險就不會影響到通常的用戶級指令。 <BR>2.乘法單元冒險:從 MIPS
CPU的整數(shù)乘法器得到的運算結(jié)果是互鎖的,所以取得這個運算結(jié)果的mflo指令沒有延遲指令槽。但是整數(shù)乘法硬件的獨立性產(chǎn)生了自己的問題,參看A.3節(jié)。
<BR><BR>3.協(xié)處理器0冒險:協(xié)處理器0控制指令通常用不同于平常的時序來讀/寫寄存器,這樣就產(chǎn)生了流水線問題。其中多數(shù)沒有互鎖。詳細信息必須從你的CPU用戶手冊中查,但是我們將看一下你在R4000
CPU(可能是MIPS CPU中最難處理的)上必須要做的事情。
<BR><BR>注意分枝延遲指令槽,盡管它是為了降低流水化而被引入的,但它作為MIPS架構(gòu)的一部分,因此不再是冒險了;它只是一個特例而已。
<BR><BR>A.2 避免互鎖來提高性能
<BR><BR>只要CPU發(fā)生互鎖,我們將會損失性能。但是如果用一些巧妙的法子,CPU本來是可以做些有用的事情的。我們想讓編譯器(or for heavily
used function perhaps a dedicated human programmer)重新組織代碼來得到最佳運行效果。
<BR><BR>編譯器—以及人—都發(fā)現(xiàn)這是一個挑戰(zhàn)。一個高度優(yōu)化已避免互鎖的程序經(jīng)常將運算的幾個階段分解,然后交錯的執(zhí)行,這樣就很難看出將會發(fā)生什么。如果代碼只是從原來的位置被前后移動了四,五條指令,通常還好處理。更大的移動就會出現(xiàn)越來越大的問題。
<BR>在單流水線機器(目前的絕大部分MIPS
CPU)中,絕大部分指令使用一個時鐘周期,所以對那些用四到五個時鐘周期才能完成的指令以及那些成功的和其他指令交迭的指令,我們都有希望重新組織它們。在MIPS
CPU中,這些標準只有對那些浮點指令才能很好的符合,所以高級調(diào)度機制會提高浮點指令的性能但對整數(shù)指令卻作用無幾(1)。深入討論這個問題超出了本書的范圍;如果你想很好的回顧一下所使用的編譯器技術,請看一下Hennessy
and Patterson, <<計算機體系結(jié)構(gòu):一種量化的方法>>。如果想知道各種CPU的詳細時序,請查一下相應的用戶手冊。
<BR>在第十二章第388頁有一個小規(guī)模的關于load互鎖的代碼優(yōu)化的例子。 <BR><BR>A.3 乘法單元冒險:早期修改lo and hi。
<BR><BR>當一個MIPS
CPU發(fā)生了中斷或異常時,大部分流水線中的指令被中止,并且禁止將運算的結(jié)果寫回。但是整數(shù)乘法單元很少與CPU的其余部分有關聯(lián),因此繼續(xù)運行,
這并不會對異常產(chǎn)生影響。這意味著一旦乘法和除法指令開始以后,不能防止改變乘法單元的運算結(jié)果寄存器的lo and hi。
<BR>異常可能及時發(fā)生以防止mfhi或者mflo完成寫回操作,但可能允許后續(xù)的乘法或者除法指令開始運行—并且一旦第二個運算開始以后原來的數(shù)據(jù)將會丟失。
<BR>為了避免這個問題,確保至少用兩個時鐘周期從后面的乘法或除法指令分離mfhi或者mflo指令,那么在所有的MIPS
CPU上都是足夠的了。好的編譯器和匯編器將會為你處理這些的。而且只有你反匯編這些代碼,你才會知道它的存在,這樣你會發(fā)現(xiàn)一些沒有料想到的nop指令。
<BR><BR>----------------------------------------------------------------------------------------------------
<BR>1. 這是為何SGI編譯器在高度浮點化程序上快得多的一個原因—可能快30%,但在整數(shù)代碼上比GNU C差一點點。 <BR><BR><BR><BR>A.4
避免協(xié)處理器0冒險:有多少nop呢? <BR>程序員的問題是,我需要在一對特定指令之間放多少條指令(很可能是nops)才能讓它們安全的工作呢?
<BR>原則上是可以得到指令對和在它們之間需要多少時鐘周期的一份完整清單。但是那太費時間了。但我們能夠降低這個工作的規(guī)模,我們注意到只有在以下情況時問題只會出現(xiàn):
<BR><BR>1.使用比標準時間(標準時間就是ALU階段的末端)長的時間來產(chǎn)生數(shù)據(jù)的指令和/或
<BR>2.指令需要使用在標準時間(在這種情況下,標準時間是ALU流水階段的開始)前準備好的數(shù)據(jù)
<BR><BR><BR>我們不需要列出在標準時刻產(chǎn)生和使用數(shù)據(jù)的指令,只要列出那些偏離正常途徑的指令就可以了。對于其中的每一條指令,當運算結(jié)果產(chǎn)生了以及/或者當需要操作數(shù)時(1)我們都需要當心。有了這些,我們將能夠在最復雜的情況下得到正確的或者高效的指令序列。
<BR>表A.1展示了R4000/4400
CPU的時序,這張圖原來出現(xiàn)在Heinrich,<<R4000/R4400用戶手冊>>(在參考書目可以找到Web地址)中。這個表列出了操作數(shù)被使用和運算結(jié)果對后續(xù)的指令變?yōu)榭捎玫牧魉A段。
<BR><BR>1. 列出運算結(jié)果遲了多少時鐘周期的或者操作數(shù)早了多少時鐘周期將是足夠的---也是最簡單的。但是MIPS系列的圖表采用了流水線階段。
<BR><BR><BR>表 A.1 有可能冒險的協(xié)處理器0指令和R4000/R4400 CPU的事件時序
<BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR><BR>在一對有依賴關系的指令之間需要的時鐘周期數(shù)(通常就是nop指令的數(shù)目)是:
<BR>ResultPipestage – OperandPipestage – 1
<BR><BR>為什么要減1呢?在第n+1流水時期產(chǎn)生的運算結(jié)果和一個在第n流水時期必需的操作數(shù)產(chǎn)生了理想的流水線,所以不需要nop。實際上減1是流水線運行階段的人工模擬。
<BR>對于其他大部分MIPS
CPU你會在相應的用戶手冊中發(fā)現(xiàn)一張相似的表。這里我們用R4000/4400作為例子是因為它長長的流水線(你會在表中看到總共有8級流水);以及作為MIPS
III CPU家族的最早產(chǎn)品的地位就意味著,在R4000上很好運行的任何代碼序列在任何后續(xù)的CPU上都會是安全的(盡管可能不是最優(yōu)的)。
<BR>注意盡管mfc0指令是在后期把數(shù)據(jù)傳送到它的目標通用寄存器,這個滯后的的運算結(jié)果在表中并沒有標注出來;這是因為它是互鎖的。這張表僅僅列出了可能引起冒險的時序。
<BR><BR><BR>A.5 協(xié)處理器0指令/指令調(diào)度 <BR><BR>我們在第6章看到了下面的在64位CPU(32位地址空間)上處理TLB撲空的一段代碼:
<BR><BR>.set normorder <BR>.set noat <BR><BR>TLBmissR4K: <BR>dmfc0 k1,
C0_CONTEXT <BR>nop #1 <BR>lw k0, 0(k1) <BR>lw k1, 8(k1) <BR>mtc0 k0, C0_ENTRYLO0
<BR>mtc0 k1, C0_ENTRYLO1 <BR>nop #(2) <BR>tlbwr # <BR>nop #(3) <BR>eret #(4)
<BR>nop #(5) <BR><BR>.set at <BR>.set reorder <BR><BR>現(xiàn)在我們能夠說明這里邊的nops指令的數(shù)目了。
<BR><BR>(1) R4000
CPU和它的大多數(shù)后代不能向下一條指令傳遞協(xié)處理器0寄存器值;dmfc0指令時序和load指令很象。Heinrich的<<R4000/R4400用戶手冊>>暗示這個操作在R4000上可能會被完全互鎖,并且可以肯定的是任何超過一個時鐘周期的延遲都會互鎖。但是它也沒有變得簡單一些,并且這里的nop對性能也不會產(chǎn)生任何不利的影響,所以我們把它保留在里邊。
<BR><BR>(2)
從表A.1,mtc0在流水線的第7步寫EntryLo1寄存器,而tlbwr指令需要數(shù)據(jù)在流水線的第5步準備好。所以只需要一個nop(是這樣計算的,7 – 5 -
1)。對于一些其它的CPU可能并不需要這個nop,但是為了可移植性的原因,還是值得保留下它的。 <BR><BR>(3)
tlbwr沒有明顯的相關性,但事實上非常重要的一點是在我們返回到用戶態(tài)代碼前,它所有的邊際作用都會被完成。tlbwr只有到流水線的第8步才能完成寫TLB,而正常指令的預取需要TLB在流水線的第二步準備好;我們必須在tlbwr和異常返回之間保留5個指令槽。eret后面跟著它的分枝延遲指令槽—在這種情況下在表中(5)處有一個nop—而且(由于R4000的長長的流水線的緣故)流水線會在分枝指令后面回填充一個”兩時鐘周期”的延遲。盡管如此,還是只有四條指令;所以我們需要在表中(3)處eret之前添加一個nop。
<BR><BR>(4)
另外一個依賴性存在于eret之間,eret會將狀態(tài)寄存器SR(EXL)域復位到它正常的用戶態(tài),并且是用戶程序的第一個指令預取狀態(tài)。但是,這個時序超出程序員的能力之外,所以機器已經(jīng)內(nèi)置了,分枝延遲時間槽加上”兩時鐘周期”如此之長的分枝延遲足夠了。
<BR><BR>有了這張表,你應該能夠做任何事! <BR><BR>A.6 協(xié)處理器0標志和指令
<BR><BR>正如前面我們看到的,一些CPU控制寄存器(協(xié)處理器0)包含了位字段值或者標志,這些位字段值或者標志有其他指令運行而產(chǎn)生的副作用。一個常用的經(jīng)驗方法是假設在執(zhí)行了一條mtc0指令后,三個指令周期內(nèi)任何這樣的副作用將是不可預知的,
<BR>但是下面的情況需要特別注意:
<BR><BR>1.啟用/禁用一組協(xié)處理指令:如果你通過改變SR(CU)中的一位而啟用了一個協(xié)處理器(使它特定的指令可用),
mtc0指令在流水線第7步生效,因此新的運算值必須在協(xié)處理器指令的流水線第2步穩(wěn)定下來。所以在這種情況下需要發(fā)射四個中間指令。
<BR><BR>2.啟用/禁用中斷:如果你寫SR(IE), SR(IM),
或者SR(EXL)而改變了CPU的中斷狀態(tài),表A.1告訴我們在流水線的第7步開始生效。中斷信號在指令流水線的第3步被采樣,以決定是繼續(xù)處理指令還是被中斷所搶占。這意味著在新的中斷狀態(tài)被安全的安裝之前,三條指令(是這樣算出的:7
– 3 - 1)必須被執(zhí)行。
<BR><BR>在三條指令運行期間,中斷能夠被檢測出來,并且能夠引發(fā)異常。但是在被中斷的指令前發(fā)射的指令改變了狀態(tài)寄存器,所以規(guī)則告訴我們狀態(tài)寄存器的改變還將會發(fā)生。
<BR><BR>設想一下你已經(jīng)通過設置異常等級位SR(EXL)禁用了中斷。你通常只會在一個地方這樣做,那就是在異常處理例程結(jié)束的地方。任何復雜情況的異常處理例程都保存異常開始處的SR值,當控制準備返回到用戶態(tài)程序時再恢復這些值,這樣異常開始處的SR(EXL)
的部分值就被設置了。 <BR><BR><BR>跟在設置SR(EXL)的指令后面有三個指令槽,
如果中斷發(fā)生這三個中的一個時,那么中斷異常發(fā)生了可SR(EXL)已經(jīng)被設置過。那將發(fā)生非常古怪的事情,包括異常返回地址EPC沒有被保存等(1)。這種情況是不可恢復的,所以當你設置SR(EXL)時確保中斷已經(jīng)被禁用是至關重要的;如果能確保你至少在三條指令時間之前清除SR(IE)和/或SR(IM),這樣你就能做到這一點。
<BR><BR>3.TLB改變和指令預取:在改變TLB和指令地址轉(zhuǎn)換生效之間有五條指令的延遲。除此之外,有一個單條目緩沖器(single entry
cache)被用于指令地址轉(zhuǎn)換(稱為微TLB),這種指令地址轉(zhuǎn)換通過加載EntryHi而被隱式的覆蓋掉;這也可能延遲生效的時間。
<BR><BR>你只有在一個未映射的地址空間運行代碼時必須顯式的更新TLB。kseg0是通常的選擇。
<BR><BR><BR>-------------------------------------------------------------------------------------------------------
<BR>1.看看第6.7.2節(jié)關于這個古怪行為的原因的討論是個好主意。 <BR> </P><BR></BODY></HTML>
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -