1. 程式人生 > >從彙編層面分析if語句和switch的效能差異

從彙編層面分析if語句和switch的效能差異

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")
}