条件控制与条件传送详解
條件控制與條件傳送詳解
提要
CSAPP3e中文譯本 3.6.5 用條件控制來(lái)實(shí)現(xiàn)條件分支 3.6.6 用條件傳送來(lái)實(shí)現(xiàn)條件分支
CSAPP3e第三章前面主要是介紹了機(jī)器級(jí)代碼的二進(jìn)制形式和匯編形式、反匯編、x86匯編的基礎(chǔ)指令、條件碼及其訪問(wèn)方式等。
在介紹到匯編語(yǔ)言的條件分支時(shí)分了兩小節(jié)(3.6.5,3.6.6)分別介紹實(shí)現(xiàn)條件分支的兩種形式:
并對(duì)這兩種方式的適用場(chǎng)景,哪種在哪些場(chǎng)景下效率更高進(jìn)行了說(shuō)明,以及一些可能會(huì)造成的錯(cuò)誤。
筆者在這里要首先指出的是,如果使用C語(yǔ)言編程遇到條件分支(當(dāng)時(shí)幾乎是肯定會(huì)遇到啦^^),大可以直接按照我們熟悉的if-else模板編寫(xiě)代碼即可,
if (test-expr){then-statement; } else{else-statement; }無(wú)論我們的C語(yǔ)言代碼是按照哪種方式編寫(xiě)的,聰明的編譯器會(huì)在保證安全的前提下自動(dòng)優(yōu)化條件分支的實(shí)現(xiàn)方式,將我們的C代碼用最合適的分支方式編譯為匯編代碼。本文內(nèi)容是為了從機(jī)器級(jí)代碼匯編和現(xiàn)代CPU工作原理的層次,來(lái)幫助理解分支程序的性能,也反過(guò)來(lái)更好地理解現(xiàn)代計(jì)算機(jī)系統(tǒng)的工作原理。
以下筆者以計(jì)算兩個(gè)整型值的差的絕對(duì)值的程序?yàn)槔?#xff0c;分析條件控制和條件傳送的區(qū)別和關(guān)系。
條件控制
要實(shí)現(xiàn)算兩個(gè)整型值的差的絕對(duì)值,我想大多數(shù)人的第一反應(yīng)是以下程序:
int abs_diff(int x, int y) {if(x < y)return y - x;elsereturn x - y; }先判斷兩個(gè)值哪個(gè)較大,然后用較大的減較小的,這個(gè)代碼完全沒(méi)有問(wèn)題,這種條件分支的實(shí)現(xiàn)形式稱為條件控制。
然后我們來(lái)編譯以下這個(gè)代碼gcc -Og -S abs_diff.c -o abs_diff.s,注意這里的-Og參數(shù)是關(guān)閉編譯器的自動(dòng)優(yōu)化,使得編譯器忠實(shí)地編譯我們的C代碼。這里筆者把a(bǔ)bs_diff.s文件的關(guān)鍵部分復(fù)制出來(lái):
abs_diff: .LFB0:.cfi_startproccmpl %esi, %edijl .L4movl %edi, %eaxsubl %esi, %eaxret .L4:movl %esi, %eaxsubl %edi, %eaxret.cfi_endproc筆者注:可以認(rèn)為x保存在寄存器%edi中,y保存在寄存器%esi中。
可以看到,編譯器確實(shí)忠實(shí)地按照我們編寫(xiě)的C代碼結(jié)構(gòu)進(jìn)行了編譯,比較兩個(gè)寄存器中的參數(shù)值,然后返回較大的值減去較小的值的結(jié)果。將這種條件控制的條件分支實(shí)現(xiàn)形式用C語(yǔ)法來(lái)表達(dá)應(yīng)該是這樣的:
int goto_absdiff(int x, int y){if (x > y){goto x_ge_y;}return y - x;x_ge_y:return x - y; }當(dāng)然,所有C語(yǔ)言老師都會(huì)要求大家不要使用goto,因?yàn)樗鼤?huì)是的程序非常難以閱讀和調(diào)試,還容易出錯(cuò)。我們這里使用goto是為了模擬匯編中的JMP類指令的行為。看到這里,想必讀者應(yīng)該能明白上面所謂的結(jié)合有條件和無(wú)條件跳轉(zhuǎn)是什么意思了,在匯編語(yǔ)言中,需要結(jié)合有條件和無(wú)條件跳轉(zhuǎn),才能利用JMP類命令實(shí)現(xiàn)在兩個(gè)分支(then-statement和else-statement)中必有一個(gè)被執(zhí)行。
條件傳送
上述函數(shù)的條件傳送的實(shí)現(xiàn)形式如下:
int abs_diff(int x, int y){int result_0 = x - y;int result_1 = y - x;if (x < y){return result_1;}else{return result_0;} }可以看到,條件傳送的分支實(shí)現(xiàn)方式是先將兩種分支的值都算出來(lái),然后再比較哪個(gè)參數(shù)較大,返回對(duì)應(yīng)的結(jié)果。編譯上述代碼gcc -Og -S abs_diff_1.c -o abs_diff_1.s,得到的關(guān)鍵匯編代碼如下:
abs_diff: .LFB0:.cfi_startprocmovl %edi, %eaxsubl %esi, %eaxmovl %esi, %edxsubl %edi, %edxcmpl %esi, %edijl .L3 .L1:rep ret .L3:movl %edx, %eaxjmp .L1.cfi_endproc沒(méi)有問(wèn)題,編譯器同樣忠實(shí)地編譯了我們的C代碼結(jié)構(gòu)。
那么,這兩種條件分支的實(shí)現(xiàn)方式到底有什么區(qū)別呢?為什么說(shuō)在保證隨機(jī)輸入的情況下,第二種的運(yùn)行速度是比第一種要快的。
條件控制和條件分支的效率
我們放開(kāi)編譯器的優(yōu)化選項(xiàng),即讓編譯器自己去優(yōu)化我們的C代碼,來(lái)編譯第一種實(shí)現(xiàn)方式條件控制。
gcc -S -O1 abs_diff.c -o abs_diff_opt.s,注意這里開(kāi)啟O1級(jí)別的編譯器優(yōu)化-O1。得到的abs_diff_opt.s的關(guān)鍵匯編代碼如下:
abs_diff: .LFB0:.cfi_startprocmovl %esi, %edxsubl %edi, %edxmovl %edi, %eaxsubl %esi, %eaxcmpl %esi, %edicmovl %edx, %eaxret.cfi_endproc大家可以對(duì)比一下上面條件傳送中得到的匯編代碼,幾乎是一樣的。所以說(shuō),在編譯器優(yōu)化之后,這段匯編代碼實(shí)際上執(zhí)行的是條件傳送的條件分支實(shí)現(xiàn)方式。也就是說(shuō),編譯器認(rèn)為,在保證安全的前提下,這段代碼使用條件傳送來(lái)實(shí)現(xiàn)比使用條件控制來(lái)實(shí)現(xiàn)要更加高效。
原理
以下內(nèi)容摘自CSAPP3e中文譯本 Page 146
為了理解為什么基于條件數(shù)據(jù)傳送的代碼會(huì)比基于條件控制轉(zhuǎn)移的代碼性能要好,我們必須了解一些關(guān)于現(xiàn)代處理器如何運(yùn)行的知識(shí)。正如我們?cè)诘?章和第5章中看到的,處理器通過(guò)流水線(pipelining)來(lái)獲得高性能,在流水線中,一條指令的處理要經(jīng)過(guò)一些列的階段,每個(gè)階段執(zhí)行所需操作的一小部分(例如,從內(nèi)存取指令,確定指令類型,從內(nèi)存讀數(shù)據(jù),執(zhí)行算術(shù)運(yùn)算,向內(nèi)存寫(xiě)數(shù)據(jù),以及更新程序計(jì)數(shù)器)。這種方法通過(guò)重疊連續(xù)指令的步驟來(lái)獲得高性能,例如,在取一條命令的同時(shí),執(zhí)行它前面一條指令的算術(shù)運(yùn)算。要做到這一點(diǎn),要求能夠事先確定要執(zhí)行的指令序列,這樣才能保持流水線中充滿了待執(zhí)行的指令。當(dāng)機(jī)器要到條件跳轉(zhuǎn)(也稱為“分支”)時(shí),只有當(dāng)分支條件求值完成后,才能決定分支往哪邊走,處理器采用非常精密的分支預(yù)測(cè)邏輯來(lái)猜測(cè)每條跳轉(zhuǎn)指令是否會(huì)執(zhí)行。只要它的猜測(cè)還比較可靠(現(xiàn)代微處理器設(shè)計(jì)試圖達(dá)到90%以上的成功率),指令流水線就會(huì)充滿著指令。另一方面,錯(cuò)誤預(yù)測(cè)一個(gè)跳轉(zhuǎn),要求處理器丟掉它為該跳轉(zhuǎn)指令后所有指令已做的工作,然后再開(kāi)始從正確位置其實(shí)的指令取填充流水線,正如我們會(huì)看到的,這樣一個(gè)錯(cuò)誤預(yù)測(cè)會(huì)招致很嚴(yán)重的處罰,浪費(fèi)大約15~30個(gè)時(shí)鐘周期,導(dǎo)致程序性能嚴(yán)重下降。
可以看到,當(dāng)輸入比較隨機(jī)的情況下,CPU是很難在條件控制方式下精準(zhǔn)地預(yù)測(cè)哪條分支會(huì)被執(zhí)行的,而錯(cuò)誤預(yù)測(cè)將付出高昂的代價(jià)(原書(shū)中有具體的錯(cuò)誤預(yù)測(cè)代價(jià)計(jì)算方式,有興趣可自查),這時(shí),我們通過(guò)條件傳送的實(shí)現(xiàn)方式,則會(huì)獲得相對(duì)更優(yōu)、更穩(wěn)定的性能。
條件傳送相當(dāng)于把原本可能浪費(fèi)在跳轉(zhuǎn)的時(shí)間用在了計(jì)算另外一條分支上,所獲得的性能提升取決于跳轉(zhuǎn)所浪費(fèi)的時(shí)間和計(jì)算另外一條分支的時(shí)間對(duì)比。不過(guò)從另一點(diǎn)來(lái)看,由于只有最后返回之前才進(jìn)行條件的判斷,條件傳送更有利于流水線一直處于滿的狀態(tài),運(yùn)行時(shí)間更加穩(wěn)定。
條件傳送并不總是可行的
那有人可能就要問(wèn)了,既然如此,我們把所有條件分支都實(shí)現(xiàn)為條件傳送的方式豈不是最優(yōu),那還要條件控制的方式做什么呢?事實(shí)上恰恰相反,條件傳送的可行情況是十分受限的,大部分情況下,編譯器會(huì)將條件分支實(shí)現(xiàn)為條件控制的形式。比如下面這個(gè)C程序(同樣來(lái)CSAPP):
long cread(long *xp){return (xp ? *xp : 0); }當(dāng)指針結(jié)果為空時(shí)返回0,否則返回指針?biāo)赶虻闹?。貌似很適合實(shí)現(xiàn)為條件傳送:
cread:movq (%rdi), %raxtestq %rdi, %rdimovl $0, %edxcmov %rdx, %raxret但實(shí)際上這個(gè)實(shí)現(xiàn)是非法的,因?yàn)榧词巩?dāng)測(cè)試為假時(shí),movq指令對(duì)xp的見(jiàn)解引用還是發(fā)生了,這將導(dǎo)致一個(gè)間接引用空指針的錯(cuò)誤。所以,必須用條件控制分支方式來(lái)編譯這段C代碼。
使用條件傳送指令,也不總是會(huì)提高代碼的效率。因?yàn)楫吘挂扔?jì)算出then-statemnt和else-statement的結(jié)果,如果這些計(jì)算比較復(fù)雜,而最終有沒(méi)有被執(zhí)行,那很多計(jì)算就被白費(fèi)了。因此,編譯器必須考慮浪費(fèi)的計(jì)算和由于分支預(yù)測(cè)錯(cuò)誤所早晨改的性能處罰之間的相對(duì)性能。根據(jù)CSAPP原書(shū)的說(shuō)明,只有當(dāng)兩個(gè)表達(dá)式都是很容易的計(jì)算時(shí),例如都只是一條加法指令,編譯器才會(huì)使用條件傳送,而通常情況下,即使許多分支預(yù)測(cè)錯(cuò)誤的開(kāi)銷會(huì)超過(guò)更復(fù)雜的計(jì)算,編譯器還是會(huì)使用條件控制轉(zhuǎn)移。筆者理解,編譯器還是相對(duì)比較保守的。
__bulitin_expect
最后再說(shuō)明一下,本文內(nèi)容是為了從機(jī)器級(jí)代碼匯編和現(xiàn)代CPU工作原理的層次,來(lái)幫助理解分支程序的性能,也反過(guò)來(lái)更好地理解現(xiàn)代計(jì)算機(jī)系統(tǒng)的工作原理。而在日常的代碼編寫(xiě)中,按照正常的邏輯來(lái)編寫(xiě)程序即可,即使有優(yōu)化的需求,現(xiàn)代編譯器都會(huì)幫你進(jìn)行優(yōu)化。
當(dāng)然,如果你非常確定哪一條分支大概率會(huì)被執(zhí)行,你也可以通過(guò)\_\_builtin_except來(lái)告訴編譯器。使用\_\_bulitin_expect這個(gè)宏來(lái)告訴編譯器這個(gè)if更有可能會(huì)選擇哪一個(gè)分支,從而讓編譯器生成出跳轉(zhuǎn)可能比較小的匯編代碼。
Ref:
https://blog.csdn.net/qq_33113661/article/details/90750145?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163081410616780366559662%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=163081410616780366559662&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-90750145.pc_search_insert_download&utm_term=%E6%9D%A1%E4%BB%B6%E6%8E%A7%E5%88%B6%EF%BC%8C%E6%9D%A1%E4%BB%B6%E4%BC%A0%E9%80%81%E4%B8%8E__builtin_expect&spm=1018.2226.3001.4187
CSAPP3e
總結(jié)
以上是生活随笔為你收集整理的条件控制与条件传送详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 小区电动车怎么管理?
- 下一篇: Copy-On-Write COW机制