X86 derlemesinde bir kaydı sıfıra ayarlamanın en iyi yolu nedir: xor, mov veya ve?


Yanıtlar:


222

TL; DR özeti : tüm CPU'lar içinxor same, same en iyi seçimdir . Başka hiçbir yöntemin ona göre avantajı yoktur ve en azından diğer yöntemlere göre bir avantajı vardır. Resmi olarak Intel ve AMD tarafından tavsiye edilir ve derleyiciler ne yapar. 64 bit modunda, hala kullanmak xor r32, r32çünkü 32 bit reg sıfır üst 32 yazma . xor r64, r64bayt israfıdır çünkü REX önekine ihtiyaç duyar.

Bundan daha da kötüsü, Silvermont xor r32,r3264 bit işlenen boyutunu değil, yalnızca ayırma olarak kabul eder. Bu nedenle , r8..r15'i sıfırladığınız için bir REX öneki hala gerekli olsa bile, kullanın xor r10d,r10d, değilxor r10,r10 .

GP tamsayı örnekleri:

xor   eax, eax       ; RAX = 0.  Including AL=0 etc.
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
xor   r10,r10       ; bad on Silvermont (not dep breaking), same as r10d everywhere else because a REX prefix is still needed for r10d or r10.
mov   eax, 0        ; doesn't touch FLAGS, but not faster and takes more bytes
 and   eax, 0        ; false dependency.  (Microbenchmark experiments might want this)
 sub   eax, eax      ; same as xor on most but not all CPUs; bad on Silvermont for example.

xor   al, al        ; false dep on some CPUs, not a zeroing idiom.  Use xor eax,eax
mov   al, 0         ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified

Bir vektör yazmacının sıfırlanması genellikle en iyi şekilde yapılır pxor xmm, xmm. Bu genellikle gcc'nin yaptığı şeydir (FP talimatlarıyla kullanılmadan önce bile).

xorps xmm, xmmmantıklı olabilir. Bir bayt daha kısadır pxor, ancak xorpsIntel Nehalem'de yürütme bağlantı noktası 5'e ihtiyaç duyarken pxorherhangi bir bağlantı noktasında (0/1/5) çalışabilir. (Nehalem'in tamsayı ile FP arasındaki 2c baypas gecikme gecikmesi genellikle alakalı değildir, çünkü sıra dışı yürütme tipik olarak yeni bir bağımlılık zincirinin başlangıcında bunu gizleyebilir).

SnB ailesi mikro mimarilerinde, xor-sıfırlamanın hiçbir çeşidi bir yürütme portuna bile ihtiyaç duymaz. AMD günü ve P6 / Core2 Intel, Nehalem-öncesi xorpsve pxor(vektör-tamsayı talimatları gibi) aynı şekilde ele alınır.

128b vektör talimatının AVX sürümünü kullanmak, reg'in üst kısmını da sıfırlar, bu nedenle vpxor xmm, xmm, xmmYMM (AVX1 / AVX2) veya ZMM (AVX512) veya gelecekteki herhangi bir vektör uzantısını sıfırlamak için iyi bir seçimdir. vpxor ymm, ymm, ymmkodlamak için fazladan bayt gerektirmez ve Intel'de aynı şekilde çalışır, ancak Zen2'den önce AMD'de daha yavaş çalışır (2 uops). AVX512 ZMM sıfırlama fazladan bayt gerektirir (EVEX öneki için), bu nedenle XMM veya YMM sıfırlama tercih edilmelidir.

XMM / YMM / ZMM örnekleri

    # Good:
 xorps   xmm0, xmm0         ; smallest code size (for non-AVX)
 pxor    xmm0, xmm0         ; costs an extra byte, runs on any port on Nehalem.
 xorps   xmm15, xmm15       ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX.  Code-size is the only penalty.

   # Good with AVX:
 vpxor xmm0, xmm0, xmm0    ; zeros X/Y/ZMM0
 vpxor xmm15, xmm0, xmm0   ; zeros X/Y/ZMM15, still only 2-byte VEX prefix

#sub-optimal AVX
 vpxor xmm15, xmm15, xmm15  ; 3-byte VEX prefix because of high source reg
 vpxor ymm0, ymm0, ymm0     ; decodes to 2 uops on AMD before Zen2


    # Good with AVX512
 vpxor  xmm15,  xmm0, xmm0     ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix).
 vpxord xmm30, xmm30, xmm30    ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD.  May be worth using only high regs to avoid needing vzeroupper in short functions.
    # Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi)
 vpxord zmm30, zmm30, zmm30    ; Without AVX512VL you have to use a 512-bit instruction.

# sub-optimal with AVX512 (even without AVX512VL)
 vpxord  zmm0, zmm0, zmm0      ; EVEX prefix (4 bytes), and a 512-bit uop.  Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.

Bkz Is-vxorps sıfırlama hızlı YMM daha xmm kayıtları ile AMD Jaguar / Bulldozer / Zen üzerinde? ve
Knights Landing'de bir veya birkaç ZMM kaydını silmenin en etkili yolu nedir?

Yarı related: hızlı yolu tüm BİR bite __m256 değerini ayarlamak için ve
1 CPU kayıt Seti tüm bit verimli da AVX512 kapsayan k0..7maske kayıtlarını. SSE / AVX vpcmpeqdbirçoğunda yıkılıyor (1'leri yazmak için hala bir uop'a ihtiyaç duysa da), ancak vpternlogdZMM regs için AVX512 bozulmuyor bile. Bir döngü içinde, özellikle AVX512 ile ALU uop ile olanları yeniden oluşturmak yerine başka bir kayıttan kopyalamayı düşünün.

Ancak sıfırlama ucuzdur: Bir döngü içinde bir xmm regini xor-sıfırlamak, vektör regs için mov-eliminasyonu olan ancak yine de xor için sıfır yazmak için bir ALU uop'a ihtiyaç duyan bazı AMD CPU'ları (Buldozer ve Zen) hariç, kopyalama kadar iyidir. -zeroing.


Xor gibi deyimleri çeşitli uarklarda sıfırlamanın özelliği

Bazı CPU'lar sub same,samesıfırlama deyimi gibi algılar xor, ancak sıfırlama deyimlerini tanıyan tüm CPU'lar tanırxor . Sadece kullanmak xorEğer CPU deyim sıfırlanması hangi tanır hangi endişe zorunda kalmamak.

xor(aksine, tanınan bir sıfırlama deyimi olmanın mov reg, 0) bazı bariz ve bazı ince avantajları vardır (özet listesi, sonra bunları genişleteceğim):

  • daha küçük kod boyutu mov reg,0. (Tüm CPU'lar)
  • sonraki kod için kısmi kayıt cezalarını önler. (Intel P6 ailesi ve SnB ailesi).
  • bir yürütme birimi kullanmaz, güç tasarrufu sağlar ve yürütme kaynaklarını serbest bırakır. (Intel SnB ailesi)
  • daha küçük uop (anlık veri yok), gerektiğinde yakındaki talimatların ödünç alınması için uop önbellek satırında yer bırakır. (Intel SnB ailesi).
  • fiziksel kayıt dosyasındaki girişleri kullanmaz . (En azından Intel SnB ailesi (ve P4), muhtemelen AMD de Intel P6 ailesi mikro mimarileri gibi ROB'de kayıt durumunu korumak yerine benzer bir PRF tasarımı kullandıklarından.)

Daha küçük makine kodu boyutu (5 yerine 2 bayt) her zaman bir avantajdır: Daha yüksek kod yoğunluğu, daha az talimat önbelleği eksikliğine ve daha iyi komut getirme ve potansiyel olarak bant genişliğini çözme olasılığına yol açar.


Intel SnB ailesi mikro mimarilerinde xor için bir yürütme birimi kullanmamanın yararı küçüktür, ancak güç tasarrufu sağlar. Yalnızca 3 ALU yürütme portuna sahip olan SnB veya IvB'de önemli olma olasılığı daha yüksektir. Haswell ve daha sonra, tamsayı ALU komutlarını işleyebilen 4 yürütme bağlantı noktasına sahiptir mov r32, imm32; bu nedenle, zamanlayıcı tarafından mükemmel karar verme ile (bu, pratikte her zaman gerçekleşmez), HSW, tümü ALU'ya ihtiyaç duysa bile saat başına 4 uops sürdürebilir. yürütme bağlantı noktaları.

Daha fazla ayrıntı için kayıtları sıfırlamakla ilgili başka bir soruya cevabıma bakın.

Bruce Dawson'ın Michael Petch'in bağlantılı olduğu blog yazısı (soruya ilişkin bir yorumda) xor, bunun bir yürütme birimine ihtiyaç duymadan kayıt yeniden adlandırma aşamasında ele alındığına işaret ediyor (kaynaşmayan etki alanında sıfır uop), ancak hala bir uop olduğu gerçeğini gözden kaçırdı kaynaşmış alanda. Modern Intel CPU'lar saat başına 4 birleşik etki alanı üretebilir ve kullanımdan kaldırabilir. Saat sınırı başına 4 sıfır buradan geliyor. Kayıt yeniden adlandırma donanım Artan karmaşıklığı sadece 4. tasarımın genişliğini sınırlayan nedenlerinden biri (Bruce üzerine seri gibi bazı çok mükemmel blog postaları yazmıştır olan FP matematik ve x87 / SSE / yuvarlama konularda yapmam, tavsiye ederim).


AMD Bulldozer ailesi CPU'larda , mov immediateaynı EX0 / EX1 tamsayı yürütme bağlantı noktalarında çalışır xor. mov reg,regAGU0 / 1 üzerinde de çalışabilir, ancak bu sadece kayıt kopyalama içindir, hemen ayar yapmak için değildir. AMD Yani AFAIK, tek avantajı xorüzerinde movkısa kodlamasıdır. Fiziksel kayıt kaynaklarını da kurtarabilir, ancak herhangi bir test görmedim.


Tanınan sıfırlama deyimleri , kısmi kayıtları tam kayıtlardan (P6 ve SnB aileleri) ayrı olarak yeniden adlandıran Intel CPU'larda kısmi kayıt cezalarını önler.

xorolacaktır üst kısımları sıfırlanmasını sahip olarak kayıt etiketi , böylece xor eax, eax/ inc al/ inc eaxön IVB CPU'lar olduğunu zamanki kısmi kayıt ceza engeller. xorOlmasa bile , IvB yalnızca yüksek 8 bitler ( AH) değiştirildiğinde ve ardından tüm kayıt okunduğunda bir birleştirme işlemine ihtiyaç duyar ve Haswell bunu bile kaldırır.

Agner Fog'un microarch kılavuzundan, s. 98 (Pentium M bölümü, SnB dahil olmak üzere sonraki bölümlerde referans alınmıştır):

İşlemci, bir kütüğün XOR'unu kendisi ile onu sıfıra ayarlıyor olarak tanır. Kayıttaki özel bir etiket, EAX = AL olacak şekilde yazmacın yüksek kısmının sıfır olduğunu hatırlar. Bu etiket bir döngüde bile hatırlanır:

    ; 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

(pg82'den): İşlemci, bir kesme, yanlış tahmin veya başka bir serileştirme olayı almadığınız sürece EAX'in üst 24 bitinin sıfır olduğunu hatırlar.

Ayrıca kılavuz onaylar ait pg82 mov reg, 0edilir değil erken P6 üzerinde en azından bir sıfırlama deyim olarak kabul PIII veya PM gibi tasarlar. Transistörleri daha sonraki CPU'larda algılamak için harcadılarsa çok şaşırırdım.


xorbayrakları ayarlar , bu da koşulları test ederken dikkatli olmanız gerektiği anlamına gelir. Yana setccbir 8bit hedefiyle maalesef yalnızca , genellikle kısmi kayıt ceza almamak için dikkat çekmek gerekir.

X86-64 setcc r/m, 16/32/64 bit için kaldırılan işlem kodlarından birini (AAM gibi) , r / m alanının kaynak-kayıt 3-bit alanında kodlanmış (yol diğer bazı tek işlenen talimatlar bunları işlem kodu bitleri olarak kullanır). Ama bunu yapmadılar ve bu x86-32 için yardımcı olmazdı.

İdeal olarak, xor/ bayrakları ayarlamanız / setcc/ tam kaydı okumalısınız:

...
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

Bu, tüm CPU'larda optimum performansa sahiptir (takılma, birleşme veya yanlış bağımlılıklar yok).

Bir bayrak belirleme talimatından önce xveya yapmak istemediğinizde işler daha karmaşık hale gelir . örneğin, bir koşulda dallanmak ve ardından aynı bayraklardan başka bir koşula setcc yapmak istiyorsunuz. örneğin cmp/jle, seteya yedek bir kaydınız yok ya da xoralınmayan kod yolunu tamamen uzak tutmak istiyorsunuz .

Bayrakları etkilemeyen tanınan sıfırlama deyimleri yoktur, bu nedenle en iyi seçim hedef mikro mimariye bağlıdır. Core2'de, bir birleştirme uopunun takılması 2 veya 3 döngüde durmaya neden olabilir. SnB'de daha ucuz görünüyor, ancak ölçmek için fazla zaman harcamadım. mov reg, 0/ Kullanmak setcceski Intel CPU'larda önemli bir cezaya neden olur ve daha yeni Intel'de hala biraz daha kötüdür.

Kullanılması setcc/ movzx r32, r8muhtemelen Intel P6 ve SNB aileler için en iyi alternatiftir eğer işaret-ayarlama öğretim değil xor sıfır önde can. Bu, x veya sıfırlamadan sonra testi tekrar etmekten daha iyi olmalıdır. ( sahf/ lahfVeya pushf/'yi düşünmeyin bile popf). IvB ortadan kaldırabilir movzx r32, r8(yani, xor-sıfırlama gibi yürütme birimi veya gecikme olmaksızın kayıt yeniden adlandırma ile başa çıkabilir ). Haswell ve daha sonra sadece düzenli ortadan kaldırmak movtalimatları, bu nedenle movzxbir yürütme birimi alır ve sıfır olmayan gecikme, yapım testi vardır / setcc/ movzxdaha kötü xor/ test / setcc, ama yine de en azından olarak test / mal olarak mov r,0/ setcc(ve çok daha iyi eski CPU'lar üzerine).

Öncelikle sıfırlama olmadan setcc/ kullanmak movzx, AMD / P4 / Silvermont'ta kötüdür çünkü alt kayıtlar için ayrı ayrı dep'leri takip etmezler. Yazıcının eski değerine dair yanlış bir açıklama olacaktır. Kullanılması mov reg, 0/ setcc/ bağımlılık-kırılmasını sıfırlanması için muhtemelen en iyi alternatiftir xor/ test / setccbir seçenek değildir.

Tabii ki, setccçıktısının 8 bitten daha geniş olmasına ihtiyacınız yoksa, hiçbir şeyi sıfırlamanıza gerek yoktur. Ancak, yakın zamanda uzun bir bağımlılık zincirinin parçası olan bir kayıt seçerseniz, P6 / SnB dışındaki CPU'lara yanlış bağımlılıklara dikkat edin. (Ayrıca, kullanmakta olduğunuz kaydı kaydedebilecek / geri yükleyebilecek bir işlev çağırırsanız, kısmi bir kayıt durmasına veya fazladan uop'a neden olmaktan kaçının.)


andhemen sıfır olması, bildiğim herhangi bir CPU'daki eski değerden bağımsız olarak özel bir kasaya sahip değildir, bu nedenle bağımlılık zincirlerini kırmaz. Hiçbir avantajı xorve birçok dezavantajı yoktur.

Eğer zaman sadece microbenchmarks yazmak için yararlıdır istediğiniz bir gecikme testin parçası olarak bir bağımlılık ama sıfırlama ve ekleyerek bilinen bir değer yaratmak istiyoruz.


Bkz http://agner.org/optimize/ microarch detayları için sıfırlama deyimler bağımlılık kırma olarak kabul edildiği de dahil olmak üzere (örneğin sub same,sameederken, bazı tümünü değil CPU'lar üzerinde xor same,sametümü üzerinde kabul edilmektedir.) movEski değerine bağımlılık zincirini kırmak yok kaydın (kaynak değerine bakılmaksızın, sıfır olsun ya da olmasın, çünkü böyle movçalışır). xoryalnızca src ve dest'in aynı yazmaç olduğu özel durumda bağımlılık zincirlerini kırar, bu nedenle özel olarak tanınan bağımlılık kesiciler movlistesinin dışında bırakılır . (Ayrıca, sıfırlama deyimi olarak tanınmadığı için, diğer faydaları da beraberinde gelir.)

İlginç bir şekilde, en eski P6 tasarımı (Pentium III aracılığıyla PPRO) vermedi tanımak xorsadece kısmi-kayıt tezgahlarda engelleme amacıyla bir sıfırlama deyim olarak, bir bağımlılık-kesici olarak -zeroing kullanarak bu nedenle bazı durumlarda değdi, hem mov sonra ve xorDep'i kırmak için bu sırayla sıfırlama ve ardından tekrar sıfırlama + dahili etiket bitini yüksek bitlerin sıfır olacağı şekilde ayarlayın, böylece EAX = AX = AL.

Agner Fog Örneği 6.17'ye bakın. microarch pdf'inde. Bunun P2, P3 ve hatta (erken mi?) PM için de geçerli olduğunu söylüyor. Bağlantılı blog gönderisine yapılan bir yorum, bu denetime sahip olanın yalnızca PPro olduğunu söylüyor, ancak Katmai PIII ve @Fanael'i bir Pentium M üzerinde test ettim ve ikimiz de gecikme için bir bağımlılığı bozmadığını gördük. bağlı imulzincir. Bu, ne yazık ki Agner Fog'un sonuçlarını doğruluyor.


TP: DR:

Kodunuzu gerçekten daha güzel hale getiriyorsa veya talimatları kaydediyorsa, movkod boyutundan başka bir performans sorunu oluşturmadığınız sürece, bayraklara dokunmaktan kaçınmak için sıfırlayın . İşaretleri patlatmaktan kaçınmak, kullanmamanın tek mantıklı nedenidir xor, ancak bazen yedek bir kaydınız varsa bayrakları ayarlayan şeyin önünde x veya sıfır yapabilirsiniz.

mov-zero önde, setccgecikme için movzx reg32, reg8sonrasına göre daha iyidir (farklı yazmaçları seçebileceğiniz Intel'de hariç), ancak daha kötü kod boyutu.


7
Çoğu aritmetik komut OP R, S, sıra dışı bir CPU tarafından R yazmacının içeriğinin, hedef olarak R kaydı ile önceki talimatlarla doldurulmasını beklemeye zorlanır; bu bir veri bağımlılığıdır. Buradaki kilit nokta, Intel / AMD yongalarının, XOR R, R ile karşılaşıldığında R yazmacındaki veri için beklemek zorunda olduğu bağımlılıkları kırmak için özel bir donanıma sahip olmasıdır ve diğer yazmaç sıfırlama talimatları için bunu yapmak zorunda değildir. Bu, XOR talimatının anında yürütülmek üzere planlanabileceği anlamına gelir ve bu nedenle Intel / AMD bunu kullanmanızı önerir .
Ira Baxter

3
@IraBaxter: Evet, ve herhangi bir karışıklıktan kaçınmak için (çünkü SO'da bu yanlış kanıyı gördüm), mov reg, srcOO CPU'ları için dep zincirlerini de kırıyor (src'nin imm32 [mem]veya başka bir kayıt olmasına bakılmaksızın ). Optimizasyon kılavuzlarında bu bağımlılıktan söz edilmez çünkü bu sadece src ve dest aynı register olduğunda meydana gelen özel bir durum değildir. Her zaman hedeflerine bağlı olmayan talimatlar için olur. (Intel'in popcnt/lzcnt/tzcnthedefe sahte bir depoya sahip olma uygulaması hariç )
Peter Cordes

2
@Zboson: Bağımlılık içermeyen bir talimatın "gecikmesi", yalnızca boru hattında bir balon varsa önemlidir. Hareket eleme için güzel, ancak sıfır gecikme avantajı, sıfır gecikme avantajı, yalnızca, bir dalın yanlış tahmin edilmesi veya uygulamanın verilerin hazır olması yerine kodu çözülmüş talimatları beklediği bir hata gibi bir şeyden sonra devreye girer. Ama evet, mov-eliminasyonu movbedava değil , sadece sıfır gecikme. "Bir yürütme portu almama" kısmı genellikle önemli değildir. Kaynaştırılmış alan verimi, özellikle darboğaz olabilir. karışımda yükler veya depolar olan.
Peter Cordes

2
Agner'a göre KNL, 64 bitlik kayıtların bağımsızlığını tanımıyor. Yani xor r64, r64bir bayt israf etmiyor. Dediğiniz gibi xor r32, r32özellikle KNL ile en iyi seçim. Daha fazlasını okumak istiyorsanız, bu mikro işlem kılavuzunun 15.7 "Özel bağımsızlık durumları" bölümüne bakın.
Z bozonu

3
ah, ihtiyacınız olduğunda "sıfır kaydı" olan eski güzel MIPS nerede ?
hayalci
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.