在x86彙編中將暫存器設定為零的最佳方法是什麼:xor,mov或?

weixin_46625830發表於2020-11-12

在x86彙編中將暫存器設定為零的最佳方法是什麼:xor,mov或?

以下所有說明都做同樣的事情:設定%eax為零。哪種方式最佳(需要最少的機器週期)?

xorl %eax, %eax

mov $0, %eax

andl $0, %eax


TL; DR摘要:xor same, same是所有CPU的最佳選擇。沒有其他方法比它有任何優勢,它至少比任何其他方法都有一些優勢。它是由英特爾和AMD正式推薦的。在64位模式下,仍然使用xor r32, r32,因為寫32位暫存器會將上面的32復位。 xor r64, r64是浪費一個位元組,因為它需要一個REX字首。

更糟糕的是,Silvermont只承認xor r32,r32破壞而不是64位運算元。因此,即使因為你將r8…r15歸零而仍然需要REX字首,請使用xor r10d,r10d,而不是xor r10,r10。

例子:

xor eax, eax ; RAX = 0

xor r10d, r10d ; R10 = 0

xor edx, edx ; RDX = 0

; small code-size alternative: cdq ; zero RDX if EAX is already zero

; SUB-OPTIMAL

xor rax,rax ; waste of a REX prefix, and extra slow on Silvermont

mov eax, 0 ; doesn’t touch FLAGS, but not faster and takes more bytes

歸零向量暫存器通常最好用pxor xmm, xmm。這通常是gcc所做的(甚至在使用FP指令之前)。

xorps xmm, xmm可以有意義。它比一個位元組短一個位元組pxor,但xorps在Intel Nehalem上需要執行埠5,同時pxor可以在任何埠(0/1/5)上執行。(Nehalem在整數和FP之間的2c旁路延遲延遲通常是不相關的,因為無序執行通常可以在新的依賴鏈的開始處隱藏它)。

在SnB系列微體系結構中,xor-zeroing的味道都不需要執行埠。在AMD和預Nehalem的P6 / 2英特爾,xorps和pxor被處理的相同方式(如向量整數指令)。

使用AVX版本的128b向量指令也會將reg的上半部分vpxor xmm, xmm, xmm歸零,因此對於歸零YMM(AVX1 / AVX2)或ZMM(AVX512)或任何將來的向量擴充套件是一個很好的選擇。 vpxor ymm, ymm, ymm但是,不需要任何額外的位元組來編碼,並且執行相同。AVX512 ZMM歸零將需要額外的位元組(對於EVEX字首),因此應首選XMM或YMM歸零。

有些CPU認為sub same,same是類似的歸零xor,但所有識別任何歸零習慣用語的CPU都能識別xor。只需使用xor,您就不必擔心哪個CPU識別哪個歸零成語。

xor(作為公認的歸零成語,不像mov reg, 0)有一些明顯的和一些微妙的優點(摘要列表,然後我將擴充套件那些):

程式碼大小比mov reg,0。(所有CPU)

避免對以後的程式碼進行部分暫存器處罰。(英特爾P6系列和SnB系列)。

不使用執行單元,節省電力並釋放執行資源。(英特爾SnB系列)

較小的uop(沒有立即資料)在uop快取行中留出空間,以便在需要時附近的指令借用。(英特爾SnB系列)。

不會使用物理暫存器檔案中的條目。(英特爾SnB系列(和P4)至少可能是AMD,因為他們使用類似的PRF設計而不是像ROB P6系列微架構那樣在ROB中保持暫存器狀態。)

較小的機器程式碼大小(2個位元組而不是5個)始終是一個優勢:更高的程式碼密度導致更少的指令快取未命中,更好的指令獲取和潛在的解碼頻寬。

在Intel SnB系列微體系結構上不使用 xor 執行單元的好處很小,但節省了功耗。它更可能與SnB或IvB有關,它只有3個ALU執行埠。Haswell以及後來有4個執行埠可以處理整數ALU指令,包括mov r32, imm32,所以通過排程程式完美決策(實際上不會發生),HSW仍然可以維持每個時鐘4個uop,即使它們都需要執行埠。

有關更多詳細資訊,請參閱我關於歸零暫存器的另一個問題的答案。

Bruce Dawson的部落格帖子 Michael Petch連結(在對問題的評論中)指出xor在註冊重新命名階段處理而不需要執行單元(在未融合域中為零uops),但錯過了它仍然是一個uop的事實在融合域中。現代英特爾CPU可以每個時鐘發出和退出4個融合域uop。這就是每時鐘限制4個零的來源。暫存器重新命名硬體的複雜性增加只是將設計寬度限制為4的原因之一。(Bruce撰寫了一些非常優秀的部落格文章,比如關於FP數學和x87 / SSE /舍入問題的系列文章,我這樣做了極力推薦)。

在AMD Bulldozer系列CPU上,mov immediate執行在相同的EX0 / EX1整數執行埠上xor。mov reg,reg也可以在AGU0 / 1上執行,但這僅用於暫存器複製,而不是用於設定。所以,據我所知,在AMD公司唯一的優勢xor過分mov的是較短的編碼。它也可能節省物理暫存器資源,但我還沒有看到任何測試。

公認的歸零成語避免了對Intel CPU的部分暫存器處罰,後者將部分暫存器與完整暫存器(P6和SnB系列)分開重新命名。

xor將標記暫存器為具有上部歸零,所以xor eax, eax/ inc al/ inc eax避免了通常的區域性暫存器懲罰該IVB預CPU具有。即使沒有xor,當AH修改高8位()然後讀取整個暫存器時,IvB只需要合併uop ,而Haswell甚至會刪除它。

來自Agner Fog的微型指南,第98頁(Pentium M部分,後面的部分包括SnB參考):

處理器將自身的XOR識別為將其設定為零。暫存器中的特殊標記會記住暫存器的高位為零,因此EAX = AL。即使在迴圈中也會記住此標記:

; Example    7.9. Partial register problem avoided in loop

xor    eax, eax

mov    ecx, 100

LL:

mov    al, [esi]

mov    [edi], eax    ; No extra uop

inc    esi

add    edi, 4

dec    ecx

jnz    LL

(來自第82頁):只要您沒有得到中斷,錯誤預測或其他序列化事件,處理器就會記住EAX的高24位為零。

該引導件的pg82還證實,mov reg, 0被未識別為歸零成語,至少在早期的設計P6像PIII或PM。如果他們花費電晶體在後來的CPU上檢測它,我會感到非常驚訝。

xor設定標誌,這意味著在測試條件時必須小心。由於setcc遺憾的是隻能使用8位目的地,因此您通常需要注意避免部分註冊處罰。

如果x86-64將一個被移除的操作碼(如AAM)重新用於16/32/64位setcc r/m,並且在r / m欄位的源暫存器3位欄位中編碼謂詞,那就太好了一些其他單運算元指令將它們用作操作碼位)。但他們沒有這樣做,無論如何這對x86-32沒有幫助。

理想情況下,您應該使用xor/ set flags setcc//讀取完整暫存器:

call some_func

xor ecx,ecx ; zero before the test

test eax,eax

setnz cl ; cl = (some_func() != 0)

add ebx, ecx ; no partial-register penalty here

這在所有CPU上都具有最佳效能(無停頓,合併uop或錯誤依賴)。

當你不想在標誌設定指令之前進行xor時,事情會變得更復雜。例如,你想在一個條件上分支,然後在同一個標誌的另一個條件下setcc。例如cmp/jle,sete您要麼沒有備用暫存器,要麼完全不使用xor未採用的程式碼路徑。

沒有公認的歸零成語不會影響標誌,因此最佳選擇取決於目標微體系結構。在Core2上,插入合併uop可能會導致2或3個週期停頓。它似乎在SnB上更便宜,但我並沒有花太多時間來測量。使用mov reg, 0/ setcc會對較舊的英特爾CPU造成重大損失,並且在較新的英特爾上仍然會有所改善。

如果你不能在標誌設定指令之前進行xor-zero,那麼使用setcc/ movzx r32, r8可能是Intel P6和SnB系列的最佳選擇。這應該比在xor-zeroing之後重複測試更好。(甚至不考慮sahf/ lahf或pushf/ popf)。IvB可以消除movzx r32, r8(即使用暫存器重新命名處理它,沒有執行單元或延遲,如xor-zeroing)。Haswell後來只消除了常規mov指令,所以movzx需要一個執行單元並且具有非零延遲,使得test / setcc/ movzx比xor/ test / 差setcc,但仍然至少和test / mov r,0/ 一樣好setcc(並且在舊CPU上要好得多)。

在AMD / P4 / Silvermont上使用setcc/ movzx沒有歸零是不好的,因為它們不會分別跟蹤子暫存器的deps。暫存器的舊值會有一個錯誤的缺陷。當/ test / 不是一個選項時,使用mov reg, 0/ setcc進行歸零/依賴性破壞可能是最好的選擇。xorsetcc

當然,如果您不需要setcc輸出寬於8位,則不需要將任何內容歸零。但是,如果選擇最近屬於長依賴關係鏈的暫存器,請注意除P6 / SnB之外的CPU的錯誤依賴性。(如果你呼叫一個可以儲存/恢復你正在使用的暫存器的函式,請注意引起部分註冊失效或額外的uop。)

and具有立即零並不是特殊的,與我所知的任何CPU上的舊值無關,因此它不會破壞依賴鏈。它沒有優點xor,也有許多缺點。

請參閱http://agner.org/optimize/獲取microarch文件,包括哪些歸零成語被識別為依賴性破壞(例如sub same,same,在某些但不是所有CPU上,而在所有CPU xor same,same上都被識別。) mov確實打破了舊值的依賴關係鏈暫存器(無論源值如何,零或不,因為這是mov有效的)。 xor只有在src和dest是同一個暫存器的特殊情況下才會斷開依賴鏈,這就是為什麼它被mov排除在特別識別的依賴斷層列表之外。(另外,因為它不被認為是歸零成語,具有其他好處。)

有趣的是,最古老的P6設計(PPro到Pentium III)並沒有認識到xor- 為了避免部分暫存器停頓而僅僅作為歸零用語,因此在某些情況下值得使用兩者。(參見Agner Fog的例子6.17。在他的microarch pdf中。他說這也適用於P2,P3,甚至(早期?)PM。 對連結部落格文章的評論說只有PPro有這種疏忽,但我’我們在Katmai PIII上進行了測試,並且@Fanael在Pentium M上進行了測試,我們都發現它沒有打破延遲限制imul鏈的依賴性。)

如果它確實使您的程式碼更好或儲存指令,那麼確保零,mov以避免觸控標誌,只要您不引入除程式碼大小之外的效能問題。但是,避免使用破壞標誌是不使用的唯一合理理由xor。

相關文章