從彙編層面分析if語句和switch的效能差異
阿新 • • 發佈:2018-12-11
1、if 語句原始碼
#include<iostream> int main() { int no = 4; if (no == 1) { printf("no is 1"); } else if (no == 2){ printf("no is 2"); } else if (no == 3){ printf("no is 3"); } else if (no == 4) { printf("no is 4"); } else if (no == 5) { printf("no is 5"); } else { printf("no is other"); } int age = 4; }
2、對以上程式碼進行反彙編,得到彙編程式碼
// 把4賦值 int no = 4; 00007FF75D0B191A mov dword ptr[no], 4 //cmp == compare 比較 (把4和1比較 if (no == 1)) 00007FF75D0B1921 cmp dword ptr[no], 1 // jne == jump not equal 不相等的時候就會跳轉,跳轉到 07FF75D0B1935h 這個地址 ,就是下面這一句程式碼: 00007FF75D0B1935 cmp dword ptr[no], 2 00007FF75D0B1925 jne main + 45h(07FF75D0B1935h) // 如果相等,繼續往下執行,輸入 no is 1 00007FF75D0B1927 lea rcx, [string "no is 1" (07FF75D0B9C28h)] //call 呼叫程式碼,也就是呼叫printf函式 00007FF75D0B192E call printf(07FF75D0B11D6h) //jmp 無條件跳轉 ,這裡是跳轉到 07FF75D0B1991h 這個地址 ,也就是執行: mov dword ptr[age], 4 (原始碼是:int age = 4)這時候已經跳出if - else 語句了 00007FF75D0B1933 jmp main + 0A1h(07FF75D0B1991h) /************************ 第二個if語句判斷,跟上面相同的邏輯 ******************************/ //cmp == compare 比較 (把4和1比較 if (no == 1)) 00007FF75D0B1935 cmp dword ptr[no], 2 00007FF75D0B1939 jne main + 59h(07FF75D0B1949h) 00007FF75D0B193B lea rcx, [string "no is 2" (07FF75D0B9C38h)] 00007FF75D0B1942 call printf(07FF75D0B11D6h) 00007FF75D0B1947 jmp main + 0A1h(07FF75D0B1991h) /************************ 第三個if語句判斷,跟上面相同的邏輯 ******************************/ 00007FF75D0B1949 cmp dword ptr[no], 3 00007FF75D0B194D jne main + 6Dh(07FF75D0B195Dh) 00007FF75D0B194F lea rcx, [string "no is 3" (07FF75D0B9C48h)] 00007FF75D0B1956 call printf(07FF75D0B11D6h) 00007FF75D0B195B jmp main + 0A1h(07FF75D0B1991h) /************************ 第四個if語句判斷,跟上面相同的邏輯 ******************************/ 00007FF75D0B195D cmp dword ptr[no], 4 00007FF75D0B1961 jne main + 81h(07FF75D0B1971h) 00007FF75D0B1963 lea rcx, [string "no is 4" (07FF75D0B9C58h)] 00007FF75D0B196A call printf(07FF75D0B11D6h) 00007FF75D0B196F jmp main + 0A1h(07FF75D0B1991h) /************************ 第五個if語句判斷,跟上面相同的邏輯 ******************************/ 00007FF75D0B1971 cmp dword ptr[no], 5 00007FF75D0B1975 jne main + 95h(07FF75D0B1985h) 00007FF75D0B1977 lea rcx, [string "no is 5" (07FF75D0B9C68h)] 00007FF75D0B197E call printf(07FF75D0B11D6h) 00007FF75D0B1983 jmp main + 0A1h(07FF75D0B1991h) /************************ 最後一個if語句判斷,跟上面相同的邏輯 ******************************/ 00007FF75D0B1985 lea rcx, [string "no is other" (07FF75D0B9C78h)] 00007FF75D0B198C call printf(07FF75D0B11D6h) /************************************* int age = 4, if語句結束的提示程式碼 **************************************/ 00007FF75D0B1991 mov dword ptr[age], 4
3、if語句的使用總結:
從上面的程式碼可以看出:如果用if語句,會把每一個條件都走一遍,知道匹配到對應的條件位置為止,所以寫if-else語句的時候,儘量把出現的概率比較大的匹配值寫在前面,例如 i== 4 或者 i == 3 會經常出現,那麼程式碼應該這麼寫,效率會更高
if (no == 3) { printf("no is 3"); } else if (no == 4) { printf("no is 4"); } else if (no == 1) { printf("no is 3"); } else if (no == 2) { printf("no is 4"); } else if (no == 5) { printf("no is 5"); } else { printf("no is other"); }
4、switch語句的底層實現
原始碼:
#include<iostream>
int main() {
int no = 4;
switch (no)
{
case 0:
printf("i is 0");
case 1:
printf("i is 1");
case 2:
printf("i is 2");
case 3:
printf("i is 3");
case 4:
printf("i is 4");
case 5:
printf("i is 5");
default:
printf("i is other");
break;
}
int age = 4;
}
5、switch 反彙編分析:
switch的原理:
通過hash 演算法,得到一個hash之後的地址值,跟if不一樣的是不需要一個一個條件去比較,而是直接算出對應的值的記憶體地址,然後跳轉到對應的記憶體地址,直接執行對應的程式碼,這是一種典型的空間換時間的方式,
// 把4賦值 int no = 4;
00007FF6F462191A mov dword ptr[no], 4
00007FF6F4621921 mov eax, dword ptr[no]
// hash 演算法,得到一個hash之後的地址值,跟if不一樣的是不需要一個一個比較,而是直接算出對應的值的記憶體地址,然後跳轉到對應的記憶體地址,直接執行對應的程式碼
// 最終算出來的記憶體地址放在eax裡面: 00007FF6F4621941 mov eax, dword ptr[rcx + rax * 4 + 119B4h],
// 然後jmp eax,無條件跳轉到eax這個地址,執行對應的程式碼,也就是case語句
00007FF6F4621924 mov dword ptr[rbp + 0F4h], eax
00007FF6F462192A cmp dword ptr[rbp + 0F4h], 5
00007FF6F4621931 ja $LN9 + 0Ch(07FF6F4621995h)
00007FF6F4621933 movsxd rax, dword ptr[rbp + 0F4h]
00007FF6F462193A lea rcx, [7FF6F4610000h] // 目標地址傳送指令
00007FF6F4621941 mov eax, dword ptr[rcx + rax * 4 + 119B4h]
00007FF6F4621948 add rax, rcx
00007FF6F462194B jmp rax
// case 0 裡面的程式碼
$LN4 :
00007FF6F462194D lea rcx, [string "i is 0" (07FF6F4629C10h)]
00007FF6F4621954 call printf(07FF6F46211D6h)
// case 1 裡面的程式碼
$LN5 :
00007FF6F4621959 lea rcx, [string "i is 1" (07FF6F4629C18h)]
00007FF6F4621960 call printf(07FF6F46211D6h)
// case 2 裡面的程式碼
$LN6 :
00007FF6F4621965 lea rcx, [string "i is 2" (07FF6F4629C20h)]
00007FF6F462196C call printf(07FF6F46211D6h)
// case 3 裡面的程式碼
$LN7 :
00007FF6F4621971 lea rcx, [string "i is 3" (07FF6F4629C30h)]
00007FF6F4621978 call printf(07FF6F46211D6h)
// case 4 裡面的程式碼
$LN8 :
00007FF6F462197D lea rcx, [string "i is 4" (07FF6F4629C40h)]
00007FF6F4621984 call printf(07FF6F46211D6h)
// case 5 裡面的程式碼
$LN9 :
00007FF6F4621989 lea rcx, [string "i is 5" (07FF6F4629C50h)]
00007FF6F4621990 call printf(07FF6F46211D6h)
// default 裡面的程式碼
00007FF6F4621995 lea rcx, [string "i is other" (07FF6F4629D48h)]
00007FF6F462199C call printf(07FF6F46211D6h)
// int age = 4
00007FF6F46219A1 mov dword ptr[age], 4
6、最後的總結:
(1)用能用switch的,儘量不要用if ,如果用了if,就要結合else if ,並且儘量把條件出現的概率比較高的,放在最前面
(2)不要小看這點點的效能優化,程式碼的效能優化其實更多的是一個習慣問題,一旦養成了這個習慣,程式碼多了,積累起來,這一點點的效能影響就很大了
(3)switch不用全部判斷是因為把switch裡面的判斷一個做了hash計算,可以直接得到地址值,然後跳到執行對應的程式碼,但其實也不是所有的switch都會通過特定演算法生成值,如果條件值比較少,就不會生成hash計算。而是直接比較
(4)蘋果官方為什麼說swift效能比oc要好,swift對於switch使用就是其中一個很好地體現,swift的switch支援範圍值,填補了oc裡面的這個功能的空虛,例如以下swift程式碼:
let no = 9
switch no{
case 0..9:
print("0-9")
case 10..20:
print("10-20")
default:
print("other")
}