一步步编写操作系统 30 cpu的分支预测简介
人在道路的分岔口時(shí)要預(yù)測哪條路能夠到達(dá)目的地,面對眾多選擇時(shí),計(jì)算機(jī)也一樣要抉擇,畢竟計(jì)算機(jī)的運(yùn)行方式是以人的思路來設(shè)計(jì)的,計(jì)算機(jī)中的抉擇其實(shí)就是人在抉擇。
cpu中的指令是在流水線上執(zhí)行。分支預(yù)測,是指當(dāng)處理器遇到一個(gè)分支指令時(shí),是該把分支左邊的指令放到流水線上還是把分支右邊的指令放在流水線上呢?
如C語言程序中的if、switch、for等語言結(jié)構(gòu),編譯器將它們編譯成匯編代碼后,在匯編一級來說,這些結(jié)構(gòu)都是用跳轉(zhuǎn)指令來實(shí)現(xiàn)的,所以,匯編語言中的無條件跳轉(zhuǎn)指令很豐富,以至于稱之為跳轉(zhuǎn)指令“族”,多的足矣應(yīng)對各種轉(zhuǎn)移方式。
舉個(gè)例子,如下面測試代碼
1 void main () { 2 int i = 0; 3 while (i < 10) { 4 i++; 5 } 6 }里面的while結(jié)構(gòu),就是執(zhí)行了10次i++。我們來看一下while結(jié)構(gòu)是如何翻譯成匯編語言的。
gcc -S -o ~/test/while.S ~/test/while.c回車,這樣gcc就將while.c編譯成了匯編代碼while.S。其中的參數(shù)-S是編譯到匯編語言,不進(jìn)行匯編和鏈接。
查看下~/test /while.S文件,cat -n ~/test/while.S回車
1 .file "while.c"2 .text3 .globl main4 .type main, @function5 main:6 pushl %ebp7 movl %esp, %ebp8 subl $16, %esp9 movl $0, -4(%ebp) 10 jmp .L2 11 .L3: ;此處是while的循環(huán)體 12 addl $1, -4(%ebp) 13 .L2: ;此處是while循環(huán)條件表達(dá)式 14 cmpl $9, -4(%ebp) 15 jle .L3 16 leave 17 ret 18 .size main, .-main 19 .ident "GCC: (GNU) 4.4.6 20120305 (Red Hat 4.4.6-4)" 20 .section .note.GNU-stack,"",@progbits這個(gè)生成的匯編語言并不是我們熟悉的intel語法,而是AT&T語法,如果此時(shí)您覺得太陌生也不要慌張,因?yàn)樵诤竺娴恼鹿?jié)我們會專門說到此類語法,現(xiàn)在先拋出來和大家預(yù)預(yù)熱。
本來打算只列出第9~15行的,但考慮到本身才20行,干脆就全貼出來了,簡要說明下,前4行是用于聲明代碼段、導(dǎo)出main函數(shù)符號。第5行是main函數(shù)起始地址,高級語言中的函數(shù)名在匯編語言中只是個(gè)符號,而符號便是地址,這就是很多教科書上都說函數(shù)名是地址的原因。話說數(shù)組也同理,數(shù)組名在匯編語言中也是個(gè)標(biāo)號地址,所以數(shù)組名也是地址。局部變量是在棧中分配空間的,所以第6~8行是在創(chuàng)建堆棧框架,也就是為局部變量i在棧中分配空間,-4(%ebp)便是指局部變量i。堆棧框架以后會說到。咱們主要是看第9~15行。
第9行是為變量i賦值為0。AT&T語法中,寄存器前要用%來指示,立即數(shù)前要用$來指示。-4(%ebp)表示內(nèi)存地址“ebp寄存器的值減4”處內(nèi)存內(nèi)容。相當(dāng)于intel匯編語法形式[ebp – 4]。AT&T語法中是源操作數(shù)在左,目的操作數(shù)在右,和intel語法相反。所以第9行是將0送入了變量i所在的棧空間。
第10行就是簡單的無條件跳轉(zhuǎn),直接進(jìn)入while循環(huán)結(jié)構(gòu)的條件表達(dá)式判斷,也就是第13行。
第14行就是while括號中的條件表達(dá)式,用變量i的值和立即數(shù)9做比較。
第15行的jle意思是,若第14行的比較結(jié)果是小于等于9,則跳到11行,繼續(xù)執(zhí)行第12行的加法。可見第11~12行則是循環(huán)體。
程序執(zhí)行流是由第15行跳到第11行,這樣組成了循環(huán)結(jié)構(gòu)的回路。
程序執(zhí)行while循環(huán)后就結(jié)束了,所以局部變量i所在的棧空間要被回收,第16行的指令leave是用于堆棧框架的回收工作。
第17行是main函數(shù)退出。由于main也是被調(diào)用的,所以gcc顯示的幫咱們加了個(gè)ret以示退出,為什么main也是由別人調(diào)用的,這個(gè)在加載用戶程序時(shí)咱們會說到的。
上面的第15行jle指令就是程序中的分支結(jié)構(gòu)。我們花了“大力氣”講述了程序流的分支,這并不是浪費(fèi)力氣。類似這樣的分支結(jié)構(gòu)很多,它們只有兩種結(jié)果,要么轉(zhuǎn)移到這一邊,要么轉(zhuǎn)移到那一邊。分支結(jié)構(gòu)雖然讓程序更加靈活多樣,但這卻成了cpu執(zhí)行效率的詬病。這是怎么回事呢?
之前說流水線的時(shí)候,我和大家強(qiáng)調(diào)了兩次“重疊”,即同一時(shí)間周期內(nèi)完成的是當(dāng)前指令的執(zhí)行,下一條指令的譯碼,第三條指令的取指。其中最重要的是“執(zhí)行”,指令只有執(zhí)行了,才真正是潑出去的水,收不回來了。另外的譯碼和取指并不重要,首先它們并不是執(zhí)行,其次它們也不屬于當(dāng)前指令,當(dāng)前指令的“取指”和“譯碼”早就在前兩個(gè)周期內(nèi)完成了。
不知道您注意到了沒有,拿表4-14的周期3來說,這一時(shí)鐘周期內(nèi)的“執(zhí)行”是指的當(dāng)前指令的執(zhí)行階段,“取指”和“譯碼”這兩個(gè)工序分別隸屬于未來要執(zhí)行的下一條指令和下下一條指令。想到這里不禁要有個(gè)疑問,這兩個(gè)未來的指令,cpu是如何確定的?如果程序一直是順序執(zhí)行的,未來無論多少條指令都可以輕易得到,都可以提前放到流水線上。可是,程序是有分支啊,到底該把哪個(gè)分支的指令放到流水線上呢?
流水線是有效提升cpu效率的方式,但流水線最大的問題是程序中的分支結(jié)構(gòu),如何把握好轉(zhuǎn)移的方向,才是使流水線保持高效的關(guān)鍵,因?yàn)槿绻魉€上的指令放錯(cuò)了的話,必須要清空那些已經(jīng)在流水線上的指令,一定不能執(zhí)行錯(cuò)誤的指令。隨著流水線級數(shù)越多,要清空的指令也將越多,清空流水線的代價(jià)就越大,這嚴(yán)重影響cpu效率。
當(dāng)遇到一個(gè)分岔口時(shí),是往左走還是往右走呢?對于這種分支情況,就需要預(yù)測出哪一側(cè)的指令將被執(zhí)行,然后將預(yù)測出的那一分支上的指令放入流水線。從統(tǒng)計(jì)學(xué)的角度來看,某些事情一旦出現(xiàn),下一次出現(xiàn)的機(jī)率還會很大。縱觀歷史,很多事情都是在重復(fù)的發(fā)生,很多偉人都拿這些歷史樣本來預(yù)測未來發(fā)生的事情。這個(gè)說的有點(diǎn)懸乎了,說點(diǎn)簡單的,比如現(xiàn)在是葡萄收獲的季節(jié),今天剛吃了葡萄,很好吃,明天后天甚至未來的幾周都會繼續(xù)吃葡萄,哈哈,我大愛葡萄。
本內(nèi)容摘自《操作系統(tǒng)真象還原》,作者不容易,請大家支持正版。
總結(jié)
以上是生活随笔為你收集整理的一步步编写操作系统 30 cpu的分支预测简介的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 老王说ros的tf库
- 下一篇: 618为何大家不疯狂“剁手”了:揭秘背后