1.什么是寄存器
所謂寄存器(register),它是CPU內(nèi)部用來存放數(shù)據(jù)的一些小型存儲區(qū)域,用來暫時存放參與運算的數(shù)據(jù)和運算結(jié)果。其實寄存器就是一種常用的時序邏輯電路,但這種時序邏輯電路只包含存儲電路。寄存器的存儲電路是由鎖存器或觸發(fā)器構(gòu)成的,因為一個鎖存器或觸發(fā)器能存儲1位二進制數(shù),所以由N個鎖存器或觸發(fā)器可以構(gòu)成N位寄存器。 2.寄存器與CPU指令 在講CPU的寄存器之前,我們先了解一下CPU指令系統(tǒng)。指令系統(tǒng)指的是一個CPU所能夠處理的全部指令的集合,Athlon XP和P4都是基于x86指令集,這是CPU的根本屬性,決定CPU運行什么樣的程序。 指令一般分為:算術(shù)邏輯運算指令、浮點運算指令、位操作指令及其他的一些非運算指令,其中整數(shù)、地址、指令指針和浮點數(shù)據(jù)是按照數(shù)據(jù)形式來劃分的。通常我們把需要CPU進行不同處理的單個數(shù)據(jù)稱為標量數(shù)據(jù)(Scala Data)。標量數(shù)據(jù)既可以是整數(shù)數(shù)據(jù),也可以是浮點數(shù)據(jù)。其中整數(shù)標量數(shù)據(jù)的存放區(qū)一般為通用寄存器(GPR),浮點標量數(shù)據(jù)的存放區(qū)一般為浮點寄存器(FPR)。與標量數(shù)據(jù)相對的是矢量數(shù)據(jù)(Vector Data),所謂矢量數(shù)據(jù)就是指一列需要由處理器作相同處理的數(shù)據(jù)集合。比如處理器在做MP3編碼的過程中,需要對內(nèi)存中的音頻文件里的各字節(jié)數(shù)據(jù)作相同的MP3編碼操作。那么通常使用MMX或SSE這類單指令多數(shù)據(jù)流(SIMD)指令,將數(shù)個字節(jié)打包為一組矢量數(shù)據(jù),存放在MMX或SSE寄存器中,再送往相應的功能單元進行統(tǒng)一操作。 其中通用寄存器是處理器中最快的存儲器,用來保存參加運算的操作數(shù)和中間結(jié)果。在通用寄存器的設計上,RISC與CISC(也就是我們常說的x86架構(gòu))有著很大的不同。CISC的寄存器通常很少——只有8個通用寄存器。由于CPU在執(zhí)行指令過程中,存在指令依賴性,在一定程度上使得x86 CPU不能在每個時鐘周期中立即發(fā)布大量的指令。所謂“依賴性”就是指令的執(zhí)行需要前個指令的運算結(jié)果。比如程序員經(jīng)常使用的分支程序,請看下面這個例子: A=C*1 只要變量A的值還不知道,B=A+2就不能進行運算。也就是說,只要指令1的結(jié)果沒有寫進寄存器,CPU調(diào)度器就不能把指令2發(fā)布到執(zhí)行單元。由于程序分支會造成具有較長流水線CPU運行停滯的,目前常用的解決方法是采用分支預測。 不過,分支預測同樣存在一個問題:流水線越長,指令潛伏期也越長,等待前一指令運算結(jié)果的時間也越長,同樣會造成CPU運行停滯。我們知道,程序指令通常都有各類型的條件分支語句,通過驗證條件決定執(zhí)行路線。但CPU執(zhí)行單元內(nèi)是通過一項特殊的預測機制選擇一條路線直接執(zhí)行(這樣可以避免驗證語句條件而處于等待情況),然后在后面進行驗證。如果預測正確則繼續(xù)往下執(zhí)行,如果發(fā)現(xiàn)以前的預測錯誤,那么就必須返回原地重新開始,以前的指令就會作廢。 因此,管線越長,意味著出現(xiàn)分支預測錯誤的機會就越多,越多在管線內(nèi)的指令會被清除掉,而且重新讓管道填滿指令的時間也會越長。對于普通處理器來說,如果出現(xiàn)分支預測錯誤,CPU就不得不將整條流水線清空后從錯誤的地方重新裝滿數(shù)據(jù)、重新執(zhí)行。毫無疑問這將花更多的時間,整體性能就會下降。因此,針對通用寄存器少的問題,在x86架構(gòu)中比較完美的解決方法就是增加寄存器的數(shù)量和采用“亂序執(zhí)行”。 3.為什么寄存器不夠用 在上面我們已經(jīng)提到,寄存器只是用來暫時存放指令值的,如果CPU需要把兩個值加起來,它需要用1個寄存器來存放運算結(jié)果,用2個寄存器來存放相加的數(shù)值。例如,在以下的方程式中:A = 2 + 4 * 在寄存器1儲存“2”; 因為在微處理器里面有超過3個寄存器,因此這個運算能夠輕易地執(zhí)行,不會造成用光寄存器的情況。 * 在寄存器1儲存 “2”; * 在寄存器2儲存“4”; * 在主內(nèi)存的某個空間儲存“寄存器1 + 寄存器2”; 我們可以看到這里使用了其它的內(nèi)存訪問過程,而在這期間其實還有我們沒有提到的其它處理過程,比如主內(nèi)存的定位也需要占據(jù)寄存器,以便讓CPU 告訴裝載/儲存單元該往哪里發(fā)送數(shù)據(jù) 。如果我們需要使用到這些結(jié)果的話,那么CPU將不得不首先到主內(nèi)存中找回這些結(jié)果,把目前滿載的寄存器驅(qū)逐一些數(shù)據(jù),把它們寫入主內(nèi)存,然后再把尋找到的數(shù)據(jù)儲存在寄存器里。 這里大家應該能夠明白吧,對內(nèi)存的訪問次數(shù)將會可怕地增加;你需要訪問內(nèi)存的時間越多,那么處理器等待工作完成的時間就越長——因而造成性能的下降。因此面對超標量CPU在并行處理大量運算,x86體系僅有的8個通用寄存器遠遠不能滿足需要,在同一時鐘周期中,如果有3個指令發(fā)布,你就需要3個輸出寄存器和6個輸入寄存器。我們該怎么辦呢?聰明的工程師們發(fā)現(xiàn)了突破這個限制的方法:“寄存器重命名”。 4.寄存器重命名技術(shù) 寄存器重命名,是CPU在解碼過程中對寄存器進行重命名,解碼器把“其它”的寄存器名字變?yōu)?#8220;通用”的寄存器名字,本質(zhì)上是通過一個表格把x86寄存器重新映射到其它寄存器,這樣可以讓實際使用到的寄存器遠大于8個。這樣做的好處除了便于前面指令發(fā)生意外或分支預測出錯時取消外,還避免了由于兩條指令寫同一個寄存器時的等待。 下面我們以一個超標量CPU執(zhí)行8個算術(shù)指令為例:假設它在每個時鐘周期中能對2個指令解碼,引出計算結(jié)果是在指令發(fā)布后3個時鐘周期發(fā)生的: (1)在第1個時鐘周期,兩個指令發(fā)布:它們互不關(guān)聯(lián),因此,它們將在3個時鐘周期后(第4個時鐘周期)引出; (2)在第2個時鐘周期,我們首次遇到了“指令依賴”,指令3需要指令2的結(jié)果,此時指令3不能開始發(fā)布; (3)如果是按序執(zhí)行,指令4、5、6就不能在指令3前發(fā)布。只有在第5個時鐘周期時(指令2的結(jié)果已得到)才能發(fā)布指令3; (4)在第6個時鐘周期有個大問題:我們想把結(jié)果寫到寄存器R1,但這將改變指令5的結(jié)果。因此,我們只有在R1空閑時(第10個時鐘周期)才能發(fā)布指令6。 按照正常情況處理的話,盡管這個CPU每個時鐘周期可以對2個指令解碼,但它每個時鐘周期的指令執(zhí)行數(shù)只有0.53。如果每次程序所需的寄存器正被使用,我們可以把數(shù)據(jù)放到其它的寄存器中,在第6個時鐘周期將寄存器R1重命名,指令6和指令8不再耽誤CPU的工作。結(jié)果是我們能夠?qū)⒚總€時鐘周期的指令執(zhí)行數(shù)提高50%。寄存器重命名技術(shù)可以使x86 CPU的寄存器可以突破8個的限制,達到32個甚至更多。寄存器重命名技術(shù)現(xiàn)在已經(jīng)深深地扎根于超標量CPU中了。 5.亂序執(zhí)行技術(shù) 除此之外,處理器工程師還引入了亂序執(zhí)行技術(shù),從一定程度上來緩解通用寄存器不足的問題。采用亂序執(zhí)行技術(shù)的目的是為了使CPU內(nèi)部電路滿負荷運轉(zhuǎn)并相應提高了CPU運行程序的速度。 這好比請A、B、C三個名人為春節(jié)聯(lián)歡晚會題寫橫幅“春節(jié)聯(lián)歡晚會”六個大字,每人各寫兩個字,如果這時在一張大紙上按順序由A寫好“春節(jié)”后再交給B寫“聯(lián)歡”,然后再由C寫“晚會”,那么這樣在A寫的時候,B和C必須等待,而在B寫的時候C仍然要等待而A已經(jīng)沒事了。但如果采用三個人分別用三張紙同時寫的做法,那么B和C都不必等待就可以同時各寫各的了,甚至C和B還可以比A先寫好也沒關(guān)系(就像亂序執(zhí)行),但當他們都寫完后就必須重新在橫幅上按“春節(jié)聯(lián)歡晚會”的順序排好(自然可以由別人做,就象CPU中亂序執(zhí)行后的重新排列單元)才能掛出去。 不過,雖然采用寄存器重命名技術(shù)、亂序執(zhí)行技術(shù),但仍不能從根本上解決x86處理器通用寄存器不足的問題。以寄存器重命名技術(shù)來說,這種技術(shù)的寄存器操作相對于RISC來說,要花費一個時鐘周期來對寄存器進行重命名,這無形中降低了處理器性能以及流水線工作效率,也增加了程序和編譯器的優(yōu)化難度。針對這個問題,最新的x86-64架構(gòu)中(K8處理器),AMD在x86架構(gòu)基礎上將通用寄存器和SIMD寄存器的數(shù)量增加了1倍:其中新增了8個通用寄存器以及8個SIMD寄存器作為原有x86處理器寄存器的擴充。 這些通用寄存器都工作在64位模式下,經(jīng)過64位編碼的程序就可以使用到它們。這些64位寄存器稱為RAX、RBX、RCX、RDX、RDI、RSI、RBP、RSP、RIP以及EFLAGS,在32位環(huán)境下并不完全使用到這些寄存器,同時AMD也將原有的EAX等寄存器擴展至64位的RAX,這樣可以增強通用寄存器對字節(jié)的操作能力。從擴充方式上看,EAX等寄存器可以看做是RAX的一個子集,系統(tǒng)仍然可以完整地執(zhí)行以往的32位編碼程序。增加通用寄存器除了可高效存儲數(shù)據(jù)外,還可作為尋址時的地址指針,從而縮短指令長度和指令執(zhí)行時間,加快CPU的運算處理速度,同時也給編程帶來方便。 此外,為了保證K8的分支預測更有效率,K8的分支預測寄存器增加到64個。分支指令可以被設為真或假,而每個指令中的6位被分配到單獨一個預測寄存器中,只有預測寄存器被設定為“真”時,那些指向預測寄存器為“真”的指令結(jié)果才會被執(zhí)行。其次由于所有的分支都能并行執(zhí)行,CPU所花的時間同只執(zhí)行單個分支的時間是相同的,降低了預測出錯的風險。第三由于CPU不再跳躍執(zhí)行,它不會把程序代碼分成小塊。也就是說,稍前和稍后的程序代碼可以打包。這樣CPU能夠一并將它們發(fā)布,增大并行工作量。從而使性能提高10%~15%,特別是在整數(shù)代碼部分。 不過在x86-64中,寄存器的擴展部分似乎僅對于整數(shù)、地址數(shù)據(jù)有效。對浮點和向量數(shù)據(jù)則仍然保持原樣。我們能從K8向64位的擴展所獲得的好處,只不過是可以在同樣一條指令中,處理更大數(shù)值的整數(shù)數(shù)值以及管理空間更大的內(nèi)存區(qū)域而已。而在32位的情況下,由于通用寄存器只能容納最大32位的數(shù)據(jù),因此顯然要花費更多條指令對尺寸超過32位的數(shù)據(jù)進行處理。這種改進對服務器、科學計算這樣的領(lǐng)域具有一定的意義,但顯然并不是普通家用環(huán)境急需的改進。 可以說,處理器的寄存器對處理器的性能有著巨大的影響。但是無論怎么發(fā)展,通用型CPU目前還沒有脫離x86架構(gòu)的限制,也許有一天,新的寄存器技術(shù)能讓我們的CPU變得更加功能強大! |
|