switch語句效能考量

2021-05-10 13:06:12 字數 3839 閱讀 7056

每年都有應屆畢業生來到公司,每年都要對新同事進行**方面的培訓,比如編碼規範就是其中之一。編碼規範初聽起來比較新鮮,但是培訓時間長了,顯然有些乏味。今年我打算改變策略,讓新同事結合已有規範文件和專案**,自己先挖掘一遍,然後大家通過坐下來討論的互動方式來加深對規範的理解,每次討論時間限制在1 hour以內,不給大家打瞌睡的機會^_^。

上週和新同事一起討論表示式和語句,說到了switch和if,談到了他們的用途和區別。大家都清楚switch語句被稱為多分支語句,當**中即將出現3個及3個以上分支時,推薦用switch,這樣**可讀性好,清晰,格式工整;但是同樣switch也是有侷限的,就是switch(xx)中的xx必須是整型變數;如果你的條件判斷是字串比較,就無法直接使用switch了。switch的這一侷限實際上是有原因的,為什麼呢?在於其效能優化。那switch語句在底層到底是如何實現的呢?和if語句相比,switch除了美觀之外,優勢又在**呢?我們唯有到彙編層去看個究竟了。

我們先來看看if多分支的情況://windows xp + gcc v3.4.2 (mingw-special)

//testif.c

int test_if_performance(int i) else if (rv == 11) else if (rv == 12) else if (rv == 13||rv == 14 || rv == 15) else

return rv; }

我們通過-s選項得到test_if_performance的彙編**,我們加上了-o2的優化選項:

//gcc -s o2 testif.c

//testif.s

... ...

_test_if_performance:

pushl    %ebp

movl    %esp, %ebp

movl    8(%ebp), %edx

cmpl    $10, %edx

je    l11

cmpl    $11, %edx

je    l12

cmpl    $12, %edx

je    l13

leal    -13(%edx), %eax

cmpl    $2, %eax

ja    l3

addl    $105, %edx

l3:popl    %ebp

movl    %edx, %eax

ret.p2align 4,,7

l11:

popl    %ebp

movl    $110, %edx

movl    %edx, %eax

ret.p2align 4,,7

l12:

popl    %ebp

movl    $112, %edx

movl    %edx, %eax

ret.p2align 4,,7

l13:

popl    %ebp

movl    $114, %edx

movl    %edx, %eax

ret從這段彙編碼來看,if語句是逐個判斷下來的,如果i = 19的話,程式需要從頭判斷到尾,"乙個都不能少"^_^。那麼擁有同樣語義功能的switch**又是如何實現的呢?我們繼續看下去。

// testswitch.c 這個檔案實現的是和上述testif.c同樣的功能

int test_switch_performance(int i)

return rv; }

我們同樣用-o2來得到switch的彙編**:

//gcc -s o2 testswitch.c

//testswitch.s

... ...

_test_switch_performance:

pushl    %ebp

movl    %esp, %ebp

movl    8(%ebp), %ecx

leal    -10(%ecx), %edx

movl    %ecx, %eax

cmpl    $5, %edx

ja    l2

jmp    *l10(,%edx,4)

.section .rdata,"dr"

.align 4

l10:

.long    l3

.long    l4

.long    l5

.long    l8

.long    l8

.long    l8

.text

.p2align 4,,7

l8:leal    105(%ecx), %eax

.p2align 4,,15

l2:popl    %ebp

ret.p2align 4,,7

l3:popl    %ebp

leal    100(%ecx), %eax

ret.p2align 4,,7

l4:popl    %ebp

leal    101(%ecx), %eax

ret.p2align 4,,7

l5:popl    %ebp

leal    102(%ecx), %eax

ret看完彙編碼,第一感覺:cmpl少了許多,乙個唯讀資料段中的l10的標籤映入眼簾,以l10標籤為起始的記憶體中依次儲存了l3、l4、l5和三個l8的位址,看起來就像是乙個位址陣列,或者是乙個位址表,訪問這個陣列中的元素實際上就是呼叫每個元素對應位址中的一段**。我們繼續往前看,來證實一下這個想法。**不多,比對著彙編指令手冊讀起來也不甚難。

pushl    %ebp

movl    %esp, %ebp        // 將棧幀位址存在%ebp中

movl    8(%ebp), %ecx        // 將rv值儲存到%ecx中

leal    -10(%ecx), %edx        // 將rv值-10之後的值,作為位址偏移量存放到%edx

movl    %ecx, %eax        // 將%ecx中的rv值儲存到%eax中

cmpl    $5, %edx            // 比較5 vs. (rv - 10),顯然5是編譯器經過**掃瞄後,算出的乙個最大偏移值

ja    l2            // jump if above ,如果5 > %edx中的值,則跳到l2繼續執行

jmp    *l10(,%edx,4)        // 如果5 <= %edx中的值,則jmp    *l10(,%edx,4)

解析一下jmp    *l10(,%edx,4),按照書中所說,*l10(,%edx,4)應該對應乙個叫indexed memory mode的模式,格式一般是base_address(offset_address, index, size),含義就是base_address + offset_address + index * size;這樣似乎就一目了然了。我們拿i = 12為例,經過前面的計算,%edx中儲存的是2,l10(,%edx,4)相當於l10 + 0 + 2 * 4,也就是起始位址=l10 + 8的那個記憶體區域,恰好是l5的起始位址,jmp    *l10(,%edx,4),直接將**執行routine轉到l5了:

l5:popl    %ebp

leal    102(%ecx), %eax

ret顯然這和前面的猜測是一致的,switch並沒有使用效能低下的逐個cmpl的方式,而是形成了乙個跳轉表(以l10為首位址的位址陣列),並將傳入switch的那個整型值經過已經的運算後作為offset值,通過乙個jmp直接轉到目的**區,這樣無論switch有多少個分支,實際上都只是做了一次cmpl,效能照比多if有很大提公升。

語句 switch語句

switch語句的特點如下 1 switch x 被選擇的內容 即x 只能是byte,short,int,char這四種型別 2 備選答案並沒有指定的順序,但是執行肯定是從第乙個case開始的,如果其中有匹配的case,執行完,通過該case的break就結束了switch。如果沒有匹配的case,...

C 效能剖析教程之switch語句

前言 幾乎每本面向初學者的c語言或c 書籍在前面兩章都會提到分支控制語句if else和switch case,在某些情況下這兩種分支控制語句可以互相替換,但卻很少有人去深究在if else和switch case語句的背後到底有什麼異同?應該選擇哪乙個語句才能使得效率最高?要回答這些問題,只能走到...

if語句和switch語句

利用if else構建分支結構if 表示式 語句1 else else部分是可選的 語句2 當表示式為真的時候,執行語句1,當表示式為假的時候,並且有else語句就執行語句2。if語句巢狀的時候,每乙個else要與最近的且沒有else語句的if進行匹配。例 if i 0 if a b else 例 ...