1. 程式人生 > 實用技巧 >兩個變數交換的三種方法的反彙編比較

兩個變數交換的三種方法的反彙編比較

先說結論:三變數交換法效率最高
起因:
學了點彙編的皮毛,想看看兩變數交換的底層到底是怎麼實現的。
主體程式碼:

//三變數交換法
int t = a;
a = b;
b = t;
//兩減一加法
a = a + b;
b = a - b;
a = a - b;
//異或法
a ^= b;
b ^= a;
a ^= b;

用VS2017進行反彙編

排版整理得

//三變數交換法
int t = a;a = b;b = t;
mov		eax,a  
mov		t,eax  
mov		eax,b  
mov		a,eax  
mov		eax,t  
mov		b,eax 

//兩減一加法
a = a + b; b = a - b; a = a - b;
mov		eax,a  
add		eax,b  
mov		a,eax  
mov		eax,a  
sub		eax,b  
mov		b,eax  
mov		eax,a  
sub		eax,b  
mov		a,eax

//異或法
a ^= b;b ^= a;a ^= b;
mov		eax,a  
xor		eax,b  
mov		a,eax  
mov		eax,b  
xor		eax,a  
mov		b,eax  
mov		eax,a  
xor		eax,b  
mov		a,eax 

觀察可知,每種方法都用到了六條mov指令,但後兩種方法卻還用到了其他的指令。
這打破了我的常規認知:用的變數越少,演算法效率越高。
不,也不是完全推翻,畢竟後兩種方法確實是減少了記憶體開銷,有著更低的空間複雜度,雖然只有一個變數那麼大。。。。。。這算是哪門子節約啊。

我原先覺得異或法最快,因為二進位制操作快,因為計算機底層就是二進位制運算。而且很多的初學者也是迷信二進位制,這種對二進位制效率的盲目認知使我寫了的a ^= b ^= a ^= b,但我從來沒有實驗過。
那麼接下來就實地測量一下:

#include <cstdio>
#include <iostream>
#include <windows.h>
using namespace std;
int a = 0,b=0;
void fun1() {
	for (int i = 0; i < 1000000000; i++) {
		int t = a;
		a = b;
		b = t;
	}
}
void fun2() {
	for (int i = 0; i < 1000000000; i++) {
		a = a + b;
		b = a - b;
		a = a - b;
	}
}
void fun3(){
	for (int i = 0; i < 1000000000; i++) {
		a = a ^ b;
		b = a ^ b;
		a = a ^ b;
	}
}
int main()
{
	a = 10;
	b = 12;
	long t1 = GetTickCount();
	fun1();
	long t2 = GetTickCount();
	fun2();
	long t3 = GetTickCount();
	fun3();
	long t4 = GetTickCount();
	cout << t2 - t1 << '\n' << t3 - t2<< '\n' << t4 - t3;
	return 0;
}

我的CPU是Core i5-6200U,windows系統,編譯器是g++,跑出來的結果是

fun1: 2937
fun2: 5781
fun3: 5594

為了排除函式執行順序的影響,我交換了三個函式的順序又測試了兩遍

fun3: 5703
fun1: 2875
fun2: 5891
fun2: 5937
fun3: 5703
fun1: 2844

結果很明顯了,三變數交換法速度是另外兩種方法的近乎兩倍。

後記:
翻了CSDN,發現前人也思考過這個問題。有很多人說異或快,也有人說異或慢。但說快的一般都是拿二進位制來說事,沒有深入分析,甚至連測個速都沒有。反觀說異或慢的,深入至彙編,給出了嚴謹詳細的證明,有理有據,令人信服。
譬如下面的二位前輩:

兩個變數交換的擴充套件思考
用異或來交換兩個變數是錯誤的

感想頗多,想說的也很多,但還是老祖宗看的透徹,一言以蔽之:

紙上學來終覺淺,絕知此事要躬行

原寫於2019年06月17日 23:41:45