2011-10-08 113 views
12

有人在幾年前向我展示了下面的命令來清零一個變量。零分配與xor,是否真的更快?

xor i,i 

他告訴我這比分配零來快。 這是真的嗎? 編譯器是否進行優化以獲取代碼執行此類事情?

+0

的可能重複[使用是否XOR章,章給優勢MOV REG,0?(http://stackoverflow.com/questions/1135679/does-using-xor-reg-reg-give-advantage-over -mov-REG-0) –

+0

['XOR EAX,eax'是零在86 ASM的寄存器(由於許多原因,不只是代碼尺寸)的最佳方式](http://stackoverflow.com/questions/33666617 /什麼是最好的方式來設置一個寄存器到零 - 在x86-assembly-xor-mov-or-and),但在C源代碼中,你應該總是寫'var = 0;'讓編譯器爲你使用xor。不要寫'var^= var',因爲它具有零優勢和許多可能的缺點(例如擊敗優化器,特別是如果var未初始化)。只發布評論,因爲這個問題似乎困惑於關於ASM與編譯器輸入的問題。 –

回答

25

你可以試試這個自己看到了答案:

movl $0,%eax 
    xor %eax,%eax 

總合,然後拆卸:

as xor.s -o xor.o 
objdump -D xor.o 

並獲得

0: b8 00 00 00 00   mov $0x0,%eax 
    5: 31 c0     xor %eax,%eax 

爲32位寄存器的MOV指令2.5倍大,需要更長的時間從RAM加載並消耗更多的緩存空間。早在一天僅加載時間是一個殺手,今天的存儲週期時間和緩存空間可以說是沒有那麼明顯,但如果你的編譯器和/或代碼,這是否過於頻繁,你會看到緩存的損失空間和/或更多的驅逐,以及更慢的系統內存週期。

在現代的CPU,更大的代碼大小還可以減慢解碼器,可能阻止他們的解碼每一週期的x86指令最大數量。 (例如,對於某些CPU,在一個16B塊中最多有4條指令)。

也有performance advantages to xor over mov in some x86 CPUs (especially Intel's) that have nothing to do with code-size,所以xor-zeroing在x86程序集中總是首選。


另一組實驗:

void fun1 (unsigned int *a) 
{ 
    *a=0; 
} 
unsigned int fun2 (unsigned int *a, unsigned int *b) 
{ 
    return(*a^*b); 
} 
unsigned int fun3 (unsigned int a, unsigned int b) 
{ 
    return(a^b); 
} 


0000000000000000 <fun1>: 
    0: c7 07 00 00 00 00  movl $0x0,(%rdi) 
    6: c3      retq 
    7: 66 0f 1f 84 00 00 00 nopw 0x0(%rax,%rax,1) 
    e: 00 00 

0000000000000010 <fun2>: 
    10: 8b 06     mov (%rsi),%eax 
    12: 33 07     xor (%rdi),%eax 
    14: c3      retq 
    15: 66 66 2e 0f 1f 84 00 nopw %cs:0x0(%rax,%rax,1) 
    1c: 00 00 00 00 

0000000000000020 <fun3>: 
    20: 89 f0     mov %esi,%eax 
    22: 31 f8     xor %edi,%eax 
    24: c3      retq 

低頭出什麼變量異或我,我在你的問題可能導致的路徑。既然你沒有指定你指的是哪個處理器或什麼上下文,就很難畫出整個圖片。例如,如果你在談論C代碼,你要明白做的代碼什麼的編譯器,這在很大程度上取決於在函數中的代碼,如果你的異時,編譯器在寄存器中,並根據操作數在你的編譯器設置上,你可能會得到xor eax,eax。或者編譯器可以選擇將其更改爲mov reg,0或更改something = 0;到一個xor reg,reg。

有些多個序列來思考:

如果地址變量已經在寄存器:

7: c7 07 00 00 00 00  movl $0x0,(%rdi) 

    d: 8b 07     mov (%rdi),%eax 
    f: 31 c0     xor %eax,%eax 
    11: 89 07     mov %eax,(%rdi) 

編譯器會選擇MOV零,而不是XOR。如果你試過這個C代碼,你會得到什麼結果:

void funx (unsigned int *a) 
{ 
    *a=*a^*a; 
} 

編譯器用移動零代替它。獲取的字節數相同,但需要訪問兩個存儲器而不是一個存儲器,並註冊一個寄存器。並執行三條指令而不是一條。所以移動零點明顯更好。現在

如果是字節大小,並在寄存器:

13: b0 00     mov $0x0,%al 
15: 30 c0     xor %al,%al 

代碼大小沒有區別。 (但他們仍然執行不同)。


現在,如果你在談論另一個處理器,可以說ARM

0: e3a00000 mov r0, #0 
    4: e0200000 eor r0, r0, r0 
    8: e3a00000 mov r0, #0 
    c: e5810000 str r0, [r1] 
    10: e5910000 ldr r0, [r1] 
    14: e0200000 eor r0, r0, r0 
    18: e5810000 str r0, [r1] 

你不使用XOR(異或,EOR)保存任何東西:一個指令一個指令都取和執行。如果你有一個寄存器中的變量地址,就像任何處理器一樣,在ram中着色。如果您必須將數據複製到另一個寄存器來執行異或操作,那麼您仍然會得到兩次內存訪問和三條指令。如果您的處理器可以將內存寫入內存,則零移動更便宜,因爲您只有一個內存訪問權限和一個或兩個指令,具體取決於處理器。

實際上它比這更糟:eor r0, r0, r0required to have an input dependency on r0(限制無序執行),因爲內存排序規則。 Xor-zeroing總是會產生零,但只會幫助x86彙編的性能。


因此,底線是要看,如果你是在彙編在x86系統上的任何地方,從8088交談寄存器到現在的XOR通常更快,因爲指令更小,讀取速度更快,佔用較少的緩存,如果您有一個,爲其他代碼留下更多緩存等。同樣,非x86可變指令長度處理器要求在指令中編碼爲0,也需要更長的指令,更長的獲取時間,如果存在高速緩存等等。所以xor更快(通常取決於它如何編碼)。如果你有條件標誌並且你想要移動/ xor設置零標誌,它會變得更糟,你可能不得不刻錄正確的指令(在某些處理器上mov不會改變標誌)。有些處理器有一個特殊的零寄存器,這是不通用的,當你使用它時,你得到一個零,你可以編碼這個非常常見的用例,而不用燒更多的指令空間或燒製一個額外的指令週期,將零立即加載到寄存器。例如,msp430,0x1234的移動會花費你一個兩字的指令,但移動0x0000或0x0001和一些其他常量可以編碼在一個指令字中。如果你正在討論RAM中的變量,讀 - 修改 - 寫兩個內存週期而不計算指令讀取次數,則所有處理器將對內存產生雙重打擊,並且如果讀取導致緩存行填充,寫入會變得更糟非常快),但是如果沒有讀取,寫入只能通過緩存並且執行速度非常快,因爲處理器可以在寫入並行進行時保持運行(有時您會獲得性能增益,有時候不會,如果您調整爲了它)。 x86和可能較舊的處理器是您看到xoring而不是移動零的習慣的原因。對於那些特定的優化,性能增益仍然存在,系統內存仍然非常緩慢,任何額外的內存週期都是昂貴的,同樣,任何被拋棄的緩存都是昂貴的。中途體面的編譯器甚至gcc會檢測出xor i,我相當於i = 0,並且根據個案情況選擇更好的(在平均系統上)指令序列。

獲得大會由邁克爾·亞伯拉什禪宗的副本。好的,使用的副本可以以合理的價格(低於50美元)獲得,即使您購買了80美元的副本,也是非常值得的。試着超越特定的8088「自行車愛好者」,並瞭解他正在嘗試教授的一般思維過程。然後花費盡可能多的時間來拆卸代碼,理想的情況是適用於許多不同的處理器。應用您學到了什麼?

+0

優秀的答案! – stdcall

5

然而,在過去的CPU上(但是在Pentium Pro之後,根據註釋),現在的大多數現代CPU都有特殊的熱路徑,用於零分配(寄存器和完全對齊的變量)應該產生相同的性能。大多數現代編譯器都傾向於混合使用這兩種編譯器(具體取決於周圍的代碼)(較早的MSVC編譯器在優化版本中始終使用XOR,並且在某些情況下仍會使用XOR,但也會使用)。

這是一個非常微觀的優化,所以tbh,你可以做任何你最好的套件,除非你有嚴格的循環由於寄存器依賴性而滯後。但應該注意的是,使用XOR大部分時間佔用較少的空間,這對於嵌入式設備或者嘗試對齊分支目標時非常有用。

這裏假定您主要是指x86及其衍生產品,那麼@Pascal給了我一個想法,即爲此提供技術參考。英特爾優化手冊分爲兩部分,即2.1.3.1 Dependancy Breaking Idioms3.5.1.7 Clearing Registers and Dependancy Breaking Idioms。這兩個部分基本支持使用基於XOR的指令進行任何形式的寄存器清除,因爲它具有依賴性斷開特性(消除延遲)。但是在條件代碼需要保存的部分,最好將0寫入寄存器。

+2

我有** no **想法你的意思是「零分配的熱路徑」。你能提供一個參考嗎?另一方面,'xor reg,reg'比Pentium Pro上的'mov reg,0'慢,因爲處理器認爲前者依賴於reg。在此之前,在這個處理器系列中沒有亂序執行,並且在那之後,處理器學會識別'xor reg,reg'獨立於'reg'的前一個值。 –

+1

@Pascal:通過「零分配熱路徑」我的意思是,微代碼優化,最小的延遲要做到這一點(由你提分手的依賴關係) – Necrolis

+5

在SandyBridge的,XOR歸零是特例,並辦理登記重命名,它甚至不使用執行端口。我從來沒有聽說過類似的技巧適用於'mov reg,0',但是如果它們存在,它會很酷,你有一個來源嗎? – harold

0

當然由於XOR指令長度較短,預取隊列內存帶寬限制是在8088真(以及在較小程度8086)。