代碼里寫很多if會影響效率嗎?


看你怎麼寫 if.

嵌入很多層if的代碼叫做「箭頭代碼」,是一個anti-pattern。 這種代碼會增加程序的循環複雜度 (Cyclomatic complexity)

具體可以看這裡:

Flattening Arrow Code

這裡:

http://wiki.c2.com/?ArrowAntiPattern

總的來說,程序里用if-else是有開銷的。每次condition的判斷就是一個計算,但是if-else的存在增加了程序語言的表達能力,提高了開發效率。

如果純粹從性能的角度看,不用if-else肯定比用好。但是在某些情況下CPU可以做到讓程序性能近似於沒有if-else的情況。stackoverflow上有一個經典問題:

Why is it faster to process a sorted array than an unsorted array?

現代CPU在運行到if的時候,不會先判斷condition然後再選擇進入if-else的哪一個分支。它們真正做的是直接進入一個分支執行,如果猜錯則回滾此分支的操作然後換另一個分支執行。如果CPU每次都猜對,就會讓程序性能接近沒有if-else的情形;反之則會造成開銷。那麼如何讓CPU猜對呢?如果condition 大部分時間都是true,少部分時間是false,那麼CPU 就比較好猜,反之,如果condition的值是一個隨機值,那麼CPU每次就得「瞎猜」,猜對的概率就會低了。

當然,如果你的if-else不是出現在循環里,一般不會造成太大開銷。

=============分割線==============================

回答一下討論區里的問題。因為我也不是做CPU的,就當拋磚引玉。希望大家指正!

首先CPU遇到if時繼續執行這個技術叫做分支預測 (branch prediction),具體可以看下面的鏈接:https://zh.wikipedia.org/zh-cn/%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%99%A8

現代CPU運行時不是一條命令一條命令的執行,而是一下讀進多條命令,並且對這些命令進行優化,生成流水線(pipeline)。優化後的流水線的運行效率是非常高的。生成流水線也是一個耗時的工作。CPU遇到if的條件語句時根據分支預測器的結果選擇跳轉還是不跳轉。並且把相應分支里的命令載入生成流水線。CPU同時還會並行地執行條件語句。如果對就繼續執行,如果錯就把當前分支的流水線拋棄然後載入另一個分支生成流水線。所以如果每次都猜對,程序的運行效率就會基本和沒有if差不多。


會有一些開銷,主要是可能破壞cpu的流水線執行,影響預讀和cache命中。一般cpu會有些分支預測的策略,大部分情況下命中率是比較高的,也可以在代碼中進行分支提示。如果你很在乎這部分性能,可以用這個

gcc的源碼里有很多例子。不過對大多數業務,除非是特別吃cpu的場景,都不需要搞這些。


會影響閱讀效率,太多if嵌套看代碼的時候辣眼睛。


如果你可以簡化if,那麼你一定要簡化,不需要有性能的原因,因為代碼你自己後面也看不懂。

不能簡化的if,即使影響性能你也做不了什麼,撐死加個likely。

其他把if藏起來的花套路都是扯淡的(尤其是利用高級語言特性),拿出來顯擺的都是半瓶子水晃悠,不知道自己在說些什麼。


不會,調試困難,閱讀困難,修改困難。


現代編譯器都很聰明,基本不會影響。其實你不用if-else,也得用其他的方式完成邏輯運算。主要是影響閱讀效率,增加修改難度。這不也會影響效率么?


對運行時間的影響就別想那麼多了,該用就用。什麼流水線,分支預測之類的話不能說錯,但if真的很難成為你系統的瓶頸。

從系統重構的角度說,很多方法,比如說多態,比如說pattern matching,都可以減少if分支數,可以讓你的程序更好維護。但這都不會給你帶來性能的提升,不減慢就燒高香了。


謝邀。

贊同 @丁衛鋒。

今天Linux環境高級編程的老師剛好講過,if嵌套太多主要是影響閱讀。


瀉藥

不確定,這要看你的編譯器有多聰明。

不過以現在編譯器的能力,我覺得寫代碼可以不用從代碼層面考慮性能,可讀性放在第一位比較好。


先說結論:會

再說處理方案:

1. 合併條件,減少判斷數量

2. 用switch取代if..else

3. 最大限度避免if嵌套

4. 對於複雜場景,用FSM模型處理


現代得cpu上性能問題不會太大...gpu上要避免..

但是這非常有礙代碼的閱讀和維護,還是要避免


首先回答你的問題,代碼里的if多了會影響性能,但通常沒有想像地大。

但是,這不是濫用if的借口,實際上,雖然處理器存在分支預測這樣的功能。在猜對的情況下能避免性能的損失,但是如果處理器猜錯了,那麼不但不能減少損失,還會浪費已經執行的指令,這些浪費最終會體現在設備折壽和電費賬單上。

另外,你也要明白精簡代碼里的if有的時候並不是真的在去掉分支。

首先演算法上的分支是無論怎麼寫代碼都不可避免的,儘管你可以使用一些技巧讓代碼里沒有if,但是這些"if"最終還是會在底層中出現,這樣的精簡沒有節約性能上的意義。

真正的精簡if是在演算法上去掉分支,舉個很簡單的例子,讓你找出0~100之間所有的偶數,如果你在每次循環中讓變數自增1,再通過取余根據餘數返回或輸出,那就不如改成從0開始每次自增2直接返回或輸出。這樣的精簡if才是真正提升了性能。

然後我介紹一下處理器的流水線加速是怎麼回事,讓大家明白為什麼處理器要使用分支預測這種執行方式,這種方式是如何避免性能上的損失的。

現在有三個指令,A,B,C。

不採用流水線加速:

執行方式:

執行A

執行B

執行C

每個指令執行完以後,才開始執行下一個指令。

採用流水線加速:

指令的執行結構將被分開為若干個「工序」,這裡我們分為3級,實際的CPU流水線級數更多。

在指令之間不依賴的情況下,流水線加速下的執行方式:

執行A(工序1)

執行B(工序1)執行A(工序2)

執行C(工序1)執行B(工序2)執行A(工序3)

執行C(工序2)執行B(工序3)

執行C(工序3)

可以發現,不採用流水線加速的情況下,指令B必須在指令A執行完之後才能執行,指令C必須在指令B執行完之後才能執行,而在流水線加速下,指令A執行完畢時,指令B已經執行了兩個工序,指令C已經執行了一個工序。

而能這樣加速的前提就是,指令之間不依賴。

如果指令之間存在依賴,比如本題中的分支。顯然,在分支之後的兩條路線中的指令依賴於判斷條件,它們無法在判斷條件未執行完的情況下開始執行,這種情況就被稱為流水線阻塞,在阻塞發生的情況下,流水線加速就不存在了。

當這個情況發生的時候,就是分支預測功能發揮作用的時候了,這個時候處理器會猜測判斷條件,並讓相應路線的指令進入流水線,如果判斷條件執行完表明猜對了,那麼皆大歡喜,加速成功。如果猜錯了,那麼流水線將被清空,已經得到的結果將被捨棄,然後開始執行另一條路線的指令,在這種情況下,加速不復存在,性能下降,同時也浪費了已經執行的指令。

現在處理器的分支預測功能已經非常強大,如果判斷條件的結果存在規律,那麼一般都能猜對,所以性能受到的影響沒有想像地大,但是我們還是要儘可能地改善演算法減少分支。


if 語句里的條件其實是一個表達式,通過返回值(false / true)選擇執行哪個分支。

既然是表達式,那麼肯定是要消耗機器周期的。所以寫多了 if 肯定是影響執行效率的。

比如下面這段代碼:

i = 1;
while ( /*Condition A*/ )
{
if (1 == i)
{
// Section 1
} // if (1 == i)

// Section 2
++i;
} // while ( ...)

這段代碼里,每次執行 while 循環都要判斷一次 i 是否為 1。而我們知道,每執行一次循環,i 都會自加 1,所以這個判斷語句除了說明一下第一次額外執行一遍語句塊 1 以外,其餘時候都沒有任何用處,if 的判斷就顯得沒有那麼必要了。不妨改成下面的形式,雖然看上去代碼多了(多了一處 Condition A),但實際執行效率卻提高了。

i = 1;
if ( /*Condition A*/ )
{
// Section 1
do
{
// Section 2
++i;
} // do
while ( /*Condition A*/ );
} // if (...)

再比如下面這段代碼:

scanf("%d", n);
if (1 == n)
{
// Section 1
} // if (1 == n)
else if (2 == n)
{
// Section 2
} // else if (2 == n)
else if ( ... )
// ...
else
{
// Section n
} // else

那麼,執行過程中,當輸入量 n 為 1 時,需要執行一次判斷;而輸入 2 時要執行兩次判斷,依此類推。這明顯沒有開關語句跳轉來得快,因為開關語句本質是一張地址表,只需訪問對應索引號的表項就能定位分支,以空間換時間,不需要其他分支的冗餘判斷:

scanf("%d", n);
switch (n)
{
case 1:
// Section 1
break; // case 1
case 2:
// Section 2
break; // case 2
// ...
default:
// Section n
break; // default
} // switch (n)


曾經試水高性能計算的負責任的告訴你,常量複雜度的計算在循環面前都是渣渣,你套一百個if不如一個十萬次流水循環影響的效率大,雖然後者可能只有幾行


對代碼的可讀性影響比較大。

如果不是時間太過緊張,一般還是想辦法重寫。

不過即使是時間再緊張的時候,也沒有緊張到寫爛代碼的地步。


如果需要做複雜的邏輯判斷,並且對各種不同的情況有多種不同的處理,那麼跳轉表是更好的選擇。

如果程序僅考慮少數幾種不同的情況,那麼你應該把複雜的判斷用邏輯運算合併。(為了可讀性)

比如

if(cc1){

if(cc2){

do sth.

}

}

不如

if(cc1cc2) {

do sth.

}

或者

if(cc1cc2) {

do sth.

}


會啊! 編譯器回進行branch prediction,如果if太多,很可能cpu提前判斷錯誤進了錯的branch。 這樣一來cpu就得滾回重新操作原來的指令,幾納秒的時間就浪費了。當然寫一般並不time critical的程序無所謂,但是如果你是微軟windows產品組的,或者高頻交易core engineering組的,那你就得注意那幾納秒的損失了。畢竟每一納秒都是錢$_$!

畢業的時候面高頻交易core engineering的職位時,經常被考到關於branch prediction的問題,還被要求改寫一道演算法題減少if來提高效率。然而其他科技企業比如谷歌並不會考到這種optimization的問題。


int i=0;

……

if (i==0)

{

return 0;

}

return 2/i;

你試試去掉if看看?

if在各個語言王國中都是重臣,不要老想著去掉他。因為很多時候直接用if比你想辦法去掉他,更省心。


影響,至少CPU的BIU中的指令緩存器在遇到跳轉命令要清理緩存器並重新取指令進行緩存,性能影響貌似不大但影響肯定是有的


如果我說

愛我沒有if

錯過就過

你是不是會難過

若if拿來當借口

那是不是有一點弱

如果我說

愛我沒有if


推薦閱讀:

Facebook 是如何在短時間內做出 「mark safe" 這個功能的?
怎麼證明我們的宇宙不是個程序?
如何寫爬蟲程序爬取豆瓣網或者新浪微博里的內容?
輪子哥的gaclib發展的怎樣?走的時什麼路線?
計算機專業大類下,還有哪些細分的專業,分別是幹什麼的?

TAG:編程 | 代碼 | Java | 程序 |