為什么程序員喜歡用大量的if else而偏不用switch,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。
站在用戶的角度思考問(wèn)題,與客戶深入溝通,找到吳興網(wǎng)站設(shè)計(jì)與吳興網(wǎng)站推廣的解決方案,憑借多年的經(jīng)驗(yàn),讓設(shè)計(jì)與互聯(lián)網(wǎng)技術(shù)結(jié)合,創(chuàng)造個(gè)性化、用戶體驗(yàn)好的作品,建站類型包括:成都做網(wǎng)站、成都網(wǎng)站設(shè)計(jì)、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣、域名注冊(cè)、網(wǎng)頁(yè)空間、企業(yè)郵箱。業(yè)務(wù)覆蓋吳興地區(qū)。
說(shuō)來(lái)也是巧最近在看 Dubbo 源碼,然后發(fā)現(xiàn)了一處很奇怪的代碼,剛好和這個(gè) switch 和 if else 有關(guān)!
讓我們來(lái)看一下這段代碼,它屬于 ChannelEventRunnable,這個(gè) runnable 是 Dubbo IO 線程創(chuàng)建,將此任務(wù)扔到業(yè)務(wù)線程池中處理.
看到?jīng)],把 state == ChannelState.RECEIVED 拎出來(lái)獨(dú)立一個(gè) if,而其他的 state 還是放在 switch 里面判斷。
我當(dāng)時(shí)腦子里就來(lái)回掃描,想想這個(gè)到底有什么花頭,奈何知識(shí)淺薄一臉懵逼。
于是就開(kāi)始了一波探險(xiǎn)之旅!
遇到問(wèn)題當(dāng)然是問(wèn)搜索引擎了,一般而言我會(huì)同時(shí)搜索各大引擎,咱這也不說(shuō)誰(shuí)比誰(shuí)好,反正有些時(shí)候度娘還是不錯(cuò)的,比如這次搜索度娘給的結(jié)果比較靠前,google 比較靠后。
一般搜索東西我都喜歡先在官網(wǎng)上搜,找不到了再放開(kāi)搜,所以先這么搜 site:xxx.com key。
你看這就有了,完美啊!
我們先來(lái)看看官網(wǎng)的這篇博客怎么說(shuō)的,然后再詳細(xì)地分析一波。
現(xiàn)代 CPU 都支持分支預(yù)測(cè) (branch prediction) 和指令流水線 (instruction pipeline),這兩個(gè)結(jié)合可以極大提高 CPU 效率。對(duì)于簡(jiǎn)單的 if 跳轉(zhuǎn),CPU 是可以比較好地做分支預(yù)測(cè)的。但是對(duì)于 switch 跳轉(zhuǎn),CPU 則沒(méi)有太多的辦法。 switch 本質(zhì)上是根據(jù)索引,從地址數(shù)組里取地址再跳轉(zhuǎn)。
也就是說(shuō) if 是跳轉(zhuǎn)指令,如果是簡(jiǎn)單的跳轉(zhuǎn)指令的話 CPU 可以利用分支預(yù)測(cè)來(lái)預(yù)執(zhí)行指令,而 switch 是要先根據(jù)值去一個(gè)類似數(shù)組結(jié)構(gòu)找到對(duì)應(yīng)的地址,然后再進(jìn)行跳轉(zhuǎn),這樣的話 CPU 預(yù)測(cè)就幫不上忙了。
然后又因?yàn)橐粋€(gè) channel 建立了之后,超過(guò)99.9%情況它的 state 都是 ChannelState.RECEIVED,因此就把這個(gè)狀態(tài)給挑出來(lái),這樣就能利用 CPU 分支預(yù)測(cè)機(jī)制來(lái)提高代碼的執(zhí)行效率。
并且還給出了 Benchmark 的代碼,就是通過(guò)隨機(jī)生成 100W 個(gè) state,并且 99.99% 是 ChannelState.RECEIVED,然后按照以下兩種方式來(lái)比一比(這 benchSwitch 官網(wǎng)的例子名字打錯(cuò)了,我一開(kāi)始沒(méi)發(fā)現(xiàn)后來(lái)校對(duì)文章才發(fā)現(xiàn))。
雖然博客也給出了它的對(duì)比結(jié)果,但是我還是本地來(lái)跑一下看看結(jié)果如何,其實(shí) JMH 不推薦在 ide 里面跑,但是我懶,直接 idea 里面跑了。
從結(jié)果來(lái)看確實(shí)通過(guò) if 獨(dú)立出來(lái)代碼的執(zhí)行效率更高(注意這里測(cè)的是吞吐),博客還提出了這種技巧可以放在性能要求嚴(yán)格的地方,也就是一般情況下沒(méi)必要這樣特殊做。
至此我們已經(jīng)知道了這個(gè)結(jié)論是對(duì)的,不過(guò)我們還需要深入分析一波,首先得看看 if 和 switch 的執(zhí)行方式到底差別在哪里,然后再看看 CPU 分支預(yù)測(cè)和指令流水線的到底是干啥的,為什么會(huì)有這兩個(gè)東西?
我們先簡(jiǎn)單來(lái)個(gè)小 demo 看看 if 和 switch 的執(zhí)行效率,其實(shí)就是添加一個(gè)全部是 if else 控制的代碼, switch 和 if + switch 的不動(dòng),看看它們之間對(duì)比效率如何(此時(shí)還是 RECEIVED 超過(guò)99.9%)。
來(lái)看一下執(zhí)行的結(jié)果如何:
好家伙,我跑了好幾次,這全 if 的比 if + switch 強(qiáng)不少啊,所以是不是源碼應(yīng)該全改成 if else 的方式,你看這吞吐量又高,還不會(huì)像現(xiàn)在一下 if 一下又 switch 有點(diǎn)不倫不類的樣子。
我又把 state 生成的值改成隨機(jī)的,再來(lái)跑一下看看結(jié)果如何:
我跑了多次還是 if 的吞吐量都是最高的,怎么整這個(gè)全 if 的都是最棒滴。
在我的印象里這個(gè) switch 應(yīng)該是優(yōu)于 if 的,不考慮 CPU 分支預(yù)測(cè)的話,單從字節(jié)碼角度來(lái)說(shuō)是這樣的,我們來(lái)看看各自生成的字節(jié)碼。
先看一下 switch 的反編譯,就截取了關(guān)鍵部分。
也就是說(shuō) switch 生成了一個(gè) tableswitch,上面的 getstatic 拿到值之后可以根據(jù)索引直接查這個(gè) table,然后跳轉(zhuǎn)到對(duì)應(yīng)的行執(zhí)行即可,也就是時(shí)間復(fù)雜度是 O(1)。
比如值是 1 那么直接跳到執(zhí)行 64 行,如果是 4 就直接跳到 100 行。
關(guān)于 switch 還有一些小細(xì)節(jié),當(dāng) swtich 內(nèi)的值不連續(xù)且差距很大的時(shí)候,生成的是 lookupswitch,按網(wǎng)上的說(shuō)法是二分法進(jìn)行查詢(我沒(méi)去驗(yàn)證過(guò)),時(shí)間復(fù)雜度是 O(logn),不是根據(jù)索引直接能找到了,我看生成的 lookup 的樣子應(yīng)該就是二分了,因?yàn)榘粗荡笮∨判蛄恕?/p>
還有當(dāng) switch 里面的值不連續(xù)但是差距比較小的時(shí)候,還是會(huì)生成 tableswtich 不過(guò)填充了一些值,比如這個(gè)例子我 switch 里面的值就 1、3、5、7、9,它自動(dòng)填充了2、4、6、8 都指到 default 所跳的行。
讓我們?cè)賮?lái)看看 if 的反編譯結(jié)果:
可以看到 if 是每次都會(huì)取出變量和條件進(jìn)行比較,而 switch 則是取一次變量之后查表直接跳到正確的行,從這方面來(lái)看 switch 的效率應(yīng)該是優(yōu)于 if 的。當(dāng)然如果 if 在第一次判斷就過(guò)了的話也就直接 goto 了,不會(huì)再執(zhí)行下面的哪些判斷了。
所以從生成的字節(jié)碼角度來(lái)看 switch 效率應(yīng)該是大于 if 的,但是從測(cè)試結(jié)果的角度來(lái)看 if 的效率又是高于 switch 的,不論是隨機(jī)生成 state,還是 99.99% 都是同一個(gè) state 的情況下。
首先 CPU 分支預(yù)測(cè)的優(yōu)化是肯定的,那關(guān)于隨機(jī)情況下 if 還是優(yōu)于 switch 的話這我就有點(diǎn)不太確定為什么了,可能是 JIT 做了什么優(yōu)化操作,或者是隨機(jī)情況下分支預(yù)測(cè)成功帶來(lái)的效益大于預(yù)測(cè)失敗的情形?
難道是我枚舉值太少了體現(xiàn)不出 switch 的效果?不過(guò)在隨機(jī)情況下 switch 也不應(yīng)該弱于 if 啊,我又加了 7 個(gè)枚舉值,一共 12 個(gè)值又測(cè)試了一遍,結(jié)果如下:
好像距離被拉近了,我看有戲,于是我背了波 26 個(gè)字母,實(shí)不相瞞還是唱著打的字母。
擴(kuò)充了分支的數(shù)量后又進(jìn)行了一波測(cè)試,這次 swtich 爭(zhēng)氣了,終于比 if 強(qiáng)了。
題外話: 我看網(wǎng)上也有對(duì)比 if 和 switch 的,它們對(duì)比出來(lái)的結(jié)果是 switch 優(yōu)于 if,首先 jmh 就沒(méi)寫對(duì),定義一個(gè)常量來(lái)測(cè)試 if 和 switch,并且測(cè)試方法的 result 寫了沒(méi)有消費(fèi),這代碼也不知道會(huì)被 JIT 優(yōu)化成啥樣了,寫了幾十行,可能直接優(yōu)化成 return 某個(gè)值了。
對(duì)比了這么多我們來(lái)小結(jié)一下。
首先對(duì)于熱點(diǎn)分支將其從 switch 提取出來(lái)用 if 獨(dú)立判斷,充分利用 CPU 分支預(yù)測(cè)帶來(lái)的便利確實(shí)優(yōu)于純 swtich,從我們的代碼測(cè)試結(jié)果來(lái)看,大致吞吐量高了兩倍。
而在熱點(diǎn)分支的情形下改成純 if 判斷而不是 if + swtich的情形下,吞吐量提高的更多。是純 switch 的 3.3 倍,是 if + switch 的 1.6 倍。
在隨機(jī)分支的情形下,三者差別不是很大,但是還是純 if 的情況最優(yōu)秀。
但是從字節(jié)碼角度來(lái)看其實(shí) switch 的機(jī)制效率應(yīng)該更高的,不論是 O(1) 還是 O(logn),但是從測(cè)試結(jié)果的角度來(lái)說(shuō)不是的。
在選擇條件少的情況下 if 是優(yōu)于 switch 的,這個(gè)我不太清楚為什么,可能是在值較少的情況下查表的消耗相比帶來(lái)的收益更大一些?有知道的小伙伴可以在文末留言。
在選擇條件很多的情況下 switch 是優(yōu)于 if 的,再多的選擇值我就沒(méi)測(cè)了,大伙有興趣可以自己測(cè)測(cè),不過(guò)趨勢(shì)就是這樣的。
接下來(lái)咱們?cè)賮?lái)看看這個(gè)分支預(yù)測(cè)到底是怎么弄的,為什么會(huì)有分支預(yù)測(cè)這玩意,不過(guò)在談到分支預(yù)測(cè)之前需要先介紹下指令流水線(Instruction pipelining),也就是現(xiàn)代微處理器的 pipeline。
CPU 本質(zhì)就是取指執(zhí)行,而取指執(zhí)行我們來(lái)看下五大步驟,分別是獲取指令(IF)、指令解碼(ID)、執(zhí)行指令(EX)、內(nèi)存訪問(wèn)(MEM)、寫回結(jié)果(WB),再來(lái)看下維基百科上的一個(gè)圖。
當(dāng)然步驟實(shí)際可能更多,反正就是這個(gè)意思需要經(jīng)歷這么多步,所以說(shuō)一次執(zhí)行可以分成很多步驟,那么這么多步驟就可以并行,來(lái)提升處理的效率。
所以說(shuō)指令流水線就是試圖用一些指令使處理器的每一部分保持忙碌,方法是將傳入的指令分成一系列連續(xù)的步驟,由不同的處理器單元執(zhí)行,不同的指令部分并行處理。
就像我們工廠的流水線一樣,我這個(gè)奧特曼的腳拼上去了馬上拼下一個(gè)奧特曼的腳,我可不會(huì)等上一個(gè)奧特曼的都組裝完了再組裝下一個(gè)奧特曼。
當(dāng)然也沒(méi)有這么死板,不一定就是順序執(zhí)行,有些指令在等待而后面的指令其實(shí)不依賴前面的結(jié)果,所以可以提前執(zhí)行,這種叫亂序執(zhí)行。
我們?cè)僬f(shuō)回我們的分支預(yù)測(cè)。
這代碼就像我們的人生一樣總會(huì)面臨著選擇,只有做了選擇之后才知道后面的路怎么走呀,但是事實(shí)上發(fā)現(xiàn)這代碼經(jīng)常走的是同一個(gè)選擇,于是就想出了一個(gè)分支預(yù)測(cè)器,讓它來(lái)預(yù)測(cè)走勢(shì),提前執(zhí)行一路的指令。
那預(yù)測(cè)錯(cuò)了怎么辦?這和咱們?nèi)松灰粯樱梢?strong>把之前執(zhí)行的結(jié)果全拋了然后再來(lái)一遍,但是也有影響,也就是流水線越深,錯(cuò)的越多浪費(fèi)的也就越多,錯(cuò)誤的預(yù)測(cè)延遲是10至20個(gè)時(shí)鐘周期之間,所以還是有副作用的。
簡(jiǎn)單的說(shuō)就是通過(guò)分支預(yù)測(cè)器來(lái)預(yù)測(cè)將來(lái)要跳轉(zhuǎn)執(zhí)行的那些指令,然后預(yù)執(zhí)行,這樣到真正需要它的時(shí)候可以直接拿到結(jié)果了,提升了效率。
分支預(yù)測(cè)又分了很多種預(yù)測(cè)方式,有靜態(tài)預(yù)測(cè)、動(dòng)態(tài)預(yù)測(cè)、隨機(jī)預(yù)測(cè)等等,從維基百科上看有16種。
我簡(jiǎn)單說(shuō)下我提到的三種,靜態(tài)預(yù)測(cè)就是愣頭青,就和蒙英語(yǔ)選擇題一樣,我管你什么題我都選A,也就是說(shuō)它會(huì)預(yù)測(cè)一個(gè)走勢(shì),一往無(wú)前,簡(jiǎn)單粗暴。
動(dòng)態(tài)預(yù)測(cè)則會(huì)根據(jù)歷史記錄來(lái)決定預(yù)測(cè)的方向,比如前面幾次選擇都是 true ,那我就走 true 要執(zhí)行的這些指令,如果變了最近幾次都是 false ,那我就變成 false 要執(zhí)行的這些指令,其實(shí)也是利用了局部性原理。
隨機(jī)預(yù)測(cè)看名字就知道了,這是蒙英語(yǔ)選擇題的另一種方式,瞎猜,隨機(jī)選一個(gè)方向直接執(zhí)行。
還有很多就不一一列舉了,各位有興趣自行去研究,順便提一下在 2018 年谷歌的零項(xiàng)目和其他研究人員公布了一個(gè)名為 Spectre 的災(zāi)難性安全漏洞,其可利用 CPU 的分支預(yù)測(cè)執(zhí)行泄漏敏感信息,這里就不展開(kāi)了,文末會(huì)附上鏈接。
之后又有個(gè)名為 BranchScope 的攻擊,也是利用預(yù)測(cè)執(zhí)行,所以說(shuō)每當(dāng)一個(gè)新的玩意出來(lái)總是會(huì)帶來(lái)利弊。
至此我們已經(jīng)知曉了什么叫指令流水線和分支預(yù)測(cè)了,也理解了 Dubbo 為什么要這么優(yōu)化了,但是文章還沒(méi)有結(jié)束,我還想提一提這個(gè) stackoverflow 非常有名的問(wèn)題,看看這數(shù)量。
這個(gè)問(wèn)題在那篇博客開(kāi)頭就被提出來(lái)了,很明顯這也是和分支預(yù)測(cè)有關(guān)系,既然看到了索性就再分析一波,大伙可以在腦海里先回答一下這個(gè)問(wèn)題,畢竟咱們都知道答案了,看看思路清晰不。
就是下面這段代碼,數(shù)組排序了之后循環(huán)的更快。
然后各路大神就蹦出來(lái)了,我們來(lái)看一下首贊的大佬怎么說(shuō)的。
一開(kāi)口就是,直擊要害。
You are a victim of branch prediction fail.
緊接著就上圖了,一看就是老司機(jī)。
他說(shuō)讓我們回到 19世紀(jì),一個(gè)無(wú)法遠(yuǎn)距離交流且無(wú)線電還未普及的時(shí)候,如果是你這個(gè)鐵路交叉口的扳道工,當(dāng)火車快來(lái)的時(shí)候,你如何得知該扳哪一邊?
火車停車再重啟的消耗是很大的,每次到分叉口都停車,然后你問(wèn)他,哥們?nèi)ツ陌?,然后扳了道,再重啟就很耗時(shí),怎么辦?猜!
猜對(duì)了火車就不用停,繼續(xù)開(kāi)。猜錯(cuò)了就停車然后倒車然后換道再開(kāi)。
所以就看猜的準(zhǔn)不準(zhǔn)了!搏一搏單車變摩托。
然后大佬又指出了關(guān)鍵代碼對(duì)應(yīng)的匯編代碼,也就是跳轉(zhuǎn)指令了,這對(duì)應(yīng)的就是火車的岔口,該選條路了。
后面我就不分析了,大伙兒應(yīng)該都知道了,排完序的數(shù)組執(zhí)行到值大于 128 的之后肯定全部大于128了,所以每次分支預(yù)測(cè)的結(jié)果都是對(duì)了!所以執(zhí)行的效率很高。
而沒(méi)排序的數(shù)組是亂序的,所以很多時(shí)候都會(huì)預(yù)測(cè)錯(cuò)誤,而預(yù)測(cè)錯(cuò)誤就得指令流水線排空啊,然后再來(lái)一遍,這速度當(dāng)然就慢了。
所以大佬說(shuō)這個(gè)題主你是分支預(yù)測(cè)錯(cuò)誤的受害者。
最終大佬給出的修改方案是咱不用 if 了,惹不起咱還躲不起嘛?直接利用位運(yùn)算來(lái)實(shí)現(xiàn)這個(gè)功能,具體我就不分析了,給大家看下大佬的建議修改方案。
而 swtich 從字節(jié)碼上看是優(yōu)于 if 的,但是從測(cè)試結(jié)果來(lái)看在分支很多的情況下能顯示出優(yōu)勢(shì),一般情況下還是打不過(guò) if 。
然后也知曉了什么叫指令流水線,這其實(shí)就是結(jié)合實(shí)際了,流水線才夠快呀,然后分支預(yù)測(cè)預(yù)執(zhí)行也是一個(gè)提高效率的方法,當(dāng)然得猜的對(duì),不然分支預(yù)測(cè)錯(cuò)誤的副作用還是無(wú)法忽略的,所以對(duì)分支預(yù)測(cè)器的要求也是很高的。
看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝您對(duì)創(chuàng)新互聯(lián)的支持。
網(wǎng)頁(yè)名稱:為什么程序員喜歡用大量的ifelse而偏不用switch
網(wǎng)頁(yè)鏈接:http://www.rwnh.cn/article4/ggojie.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供App開(kāi)發(fā)、響應(yīng)式網(wǎng)站、企業(yè)網(wǎng)站制作、網(wǎng)站收錄、網(wǎng)站制作、關(guān)鍵詞優(yōu)化
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)