2011-12-19 30 views
5

我想你們都聽說過「交換問題」; SO充滿了關於它的問題。 不使用第三個變量的交換版本通常被認爲是更快的,因爲,你有一個變量更少。我想知道發生了什麼事情的窗簾後面,並寫了下面的兩個方案:變量交換有和沒有輔助變量 - 哪個更快?

int main() { 
    int a = 9; 
    int b = 5; 
    int swap; 

    swap = a; 
    a = b; 
    b = swap; 

    return 0; 
} 

和版本,而第三個變量:

int main() { 
    int a = 9; 
    int b = 5; 

    a ^= b; 
    b ^= a; 
    a ^= b; 

    return 0; 
} 

我生成使用鐺的彙編代碼,並得到了本作第一版本(即使用第三變量):

... 
Ltmp0: 
    movq %rsp, %rbp 
Ltmp1: 
    movl $0, %eax 
    movl $0, -4(%rbp) 
    movl $9, -8(%rbp) 
    movl $5, -12(%rbp) 
    movl -8(%rbp), %ecx 
    movl %ecx, -16(%rbp) 
    movl -12(%rbp), %ecx 
    movl %ecx, -8(%rbp) 
    movl -16(%rbp), %ecx 
    movl %ecx, -12(%rbp) 
    popq %rbp 
    ret 
Leh_func_end0: 
... 

並且這對於所述第二版本(即不使用第三可變):

... 
Ltmp0: 
    movq %rsp, %rbp 
Ltmp1: 
    movl $0, %eax 
    movl $0, -4(%rbp) 
    movl $9, -8(%rbp) 
    movl $5, -12(%rbp) 
    movl -12(%rbp), %ecx 
    movl -8(%rbp), %edx 
    xorl %ecx, %edx 
    movl %edx, -8(%rbp) 
    movl -8(%rbp), %ecx 
    movl -12(%rbp), %edx 
    xorl %ecx, %edx 
    movl %edx, -12(%rbp) 
    movl -12(%rbp), %ecx 
    movl -8(%rbp), %edx 
    xorl %ecx, %edx 
    movl %edx, -8(%rbp) 
    popq %rbp 
    ret 
Leh_func_end0: 
... 

第二個更長,但我不太瞭解彙編代碼,所以我不知道這是否意味着它更慢,所以我希望聽到更多人對此有所瞭解的意見。

以上哪個版本的變量swap速度更快,佔用的內存更少?

+4

要找出哪個更快,爲什麼你不基準? – 2011-12-19 21:11:07

+0

我不知道如何測量內存使用率,另外,我也對它背後的原因感興趣。 – shutefan 2011-12-19 21:16:35

+4

它看起來不像編譯時打開優化。該組件中有很多絨毛。 – 2011-12-19 21:23:20

回答

7

看看一些優化的程序集。從

void swap_temp(int *restrict a, int *restrict b){ 
    int temp = *a; 
    *a = *b; 
    *b = temp; 
} 

void swap_xor(int *restrict a, int *restrict b){ 
    *a ^= *b; 
    *b ^= *a; 
    *a ^= *b; 
} 

gcc -O3 -std=c99 -S -o swapping.s swapping.c生產

.file "swapping.c" 
.text 
.p2align 4,,15 
.globl swap_temp 
.type swap_temp, @function 
swap_temp: 
.LFB0: 
.cfi_startproc 
movl (%rdi), %eax 
movl (%rsi), %edx 
movl %edx, (%rdi) 
movl %eax, (%rsi) 
ret 
.cfi_endproc 
.LFE0: 
.size swap_temp, .-swap_temp 
.p2align 4,,15 
.globl swap_xor 
.type swap_xor, @function 
swap_xor: 
.LFB1: 
.cfi_startproc 
movl (%rsi), %edx 
movl (%rdi), %eax 
xorl %edx, %eax 
xorl %eax, %edx 
xorl %edx, %eax 
movl %edx, (%rsi) 
movl %eax, (%rdi) 
ret 
.cfi_endproc 
.LFE1: 
.size swap_xor, .-swap_xor 
.ident "GCC: (SUSE Linux) 4.5.1 20101208 [gcc-4_5-branch revision 167585]" 
.section .comment.SUSE.OPTs,"MS",@progbits,1 
.string "Ospwg" 
.section .note.GNU-stack,"",@progbits 

對我來說,看起來swap_temp同樣有效即可。

+0

不錯的,謝謝你的優化!這是如此快/短?順便說一句,如果我交換指針而不是變量,它有什麼區別嗎? – shutefan 2011-12-20 12:38:51

+0

我敢說'swap_temp'是最優的。對於沒有'restrict'限定符的'swap_xor',gcc只產生一個指令,它變成了三個'movl a,b; xorl c,d'在每個操作中,其中一個參數是一個寄存器('%eax',總是),另一個是指針解除引用('(%rsi)'或'(%rdi)')。根據我的測量,速度較慢(但如果該功能在呼叫站點可見,則內聯可以消除該差異)。關於交換變量和交換指針之間的區別,交換變量永遠不能隱藏,所以優化通常可以完全消除它。 – 2011-12-20 13:51:50

+0

好的,謝謝並接受答案! – shutefan 2011-12-20 19:25:49

0

想要了解成本,想象一下,每個命令都需要執行成本,而間接尋址也有自己的成本。

movl -12(%rbp), %ecx 

這條線將需要像一個時間單位訪問在ECX寄存器中的值, 一個時間單位訪問RBP,另一個用於施加偏置(-12),並有更多的時間 單位(假設任意3)用於將值從存儲在ecx中的地址移動到由-12(%rbp)指示的地址 。

如果您計算每行和所有行的所有操作,第二種方法肯定比第一種方法昂貴。

+1

在這種情況下,這是真實的,但不是一般的,因爲它忽略了流水線機會。 – gnometorule 2011-12-19 21:36:44

+0

同意,但你必須知道如何優化你的代碼以優化流水線和最小化分支。我認爲我們的朋友最容易開始減少不必要的引用和過多的命令,然後再採用更先進的技術。 – 2011-12-19 22:26:00

2

XOR交換技巧的問題在於它嚴格是順序的。它看起來似乎很快,但事實上並非如此。有一條叫做XCHG的指令可以交換兩個寄存器,但由於它的原子性質,這個指令也可能比簡單地使用3 MOVs慢。常見的技術與溫度是一個很好的選擇;)

+0

-1'xchg reg1,reg2'沒有同步問題。同步問題僅出現在帶有內存操作數的'xchg'中。 – Johan 2014-04-01 09:55:50

+0

@Johan whoa,好吧,很高興知道,謝謝! :)(我確定了答案) – ScarletAmaranth 2014-04-01 10:51:42

+0

你編輯了答案,但它仍然不正確。 'XCHG reg,reg'不**具有原子性問題,因此它不低於3個'MOV's。取決於處理器,XCHG可能(或不可能)分解成多個微操作。只有'XCHG reg,[reg]'(它與一個內存位置交換一個reg)很慢,因爲它附有一個隱含的'LOCK'前綴。這是'LOCK'前綴,它會降低速度。 – Johan 2014-04-02 12:17:04