32-bit yazmaçlar üzerindeki x86-64 talimatları, tam 64-bit yazmaçların üst kısmını neden sıfırlıyor?


119

In Intel Kılavuzları X86-64 Tour , okuduğum

Belki de en şaşırtıcı gerçek, gibi bir komutun MOV EAX, EBXotomatik olarak üst 32 bitlik RAXyazmacı sıfırlamasıdır .

Aynı kaynakta alıntılanan Intel belgeleri (3.4.1.1 Kılavuz Temel Mimaride 64-Bit Modunda Genel Amaçlı Kayıtlar) bize şunları söyler:

  • 64 bitlik işlenenler, hedef genel amaçlı kayıtta 64 bitlik bir sonuç oluşturur.
  • 32 bitlik işlenenler, hedef genel amaçlı kayıtta 64 bitlik bir sonuca sıfır genişletilmiş 32 bitlik bir sonuç üretir.
  • 8 bit ve 16 bit işlenenler, 8 bitlik veya 16 bitlik bir sonuç oluşturur. Hedef genel amaçlı yazmacın üst 56 biti veya 48 biti (sırasıyla) işlem tarafından değiştirilmez. 8 bitlik veya 16 bitlik bir işlemin sonucu 64 bit adres hesaplaması için tasarlandıysa, kaydı açıkça işaretleyerek tam 64 bit'e genişletin.

X86-32 ve x86-64 derlemesinde, aşağıdaki gibi 16 bit talimatlar

mov ax, bx

eax'ın üst kelimesinin sıfırlandığı bu tür "garip" davranışı göstermeyin.

Öyleyse: bu davranışın ortaya çıkmasının nedeni nedir? İlk bakışta mantıksız görünüyor (ancak nedeni x86-32 montajının tuhaflıklarına alışmış olmam olabilir).


16
Google'da "Kısmi kayıt duraklaması" için Google'ı kullanırsanız, kaçınmaya çalıştıkları (neredeyse kesinlikle) sorun hakkında epeyce bilgi bulacaksınız.
Jerry Coffin


4
Sadece "çoğu" değil. AFAIK, bir hedef işlenen ile tüm talimatlar r32birleştirmek yerine yüksek 32'yi sıfırlar. Örneğin, bazı montajcı yerini alacak pmovmskb r64, xmmolan pmovmskb r32, xmm64bit hedef versiyonu aynı şekilde davranır, çünkü bir REX tasarruf. Olsa manuel çalıştırılması bölüm 32 dest / 64bit ve 64/128 / 256B kaynağının 6 kombinasyonları ayrı ayrı listeler, r32 formunun örtük sıfır uzatma R64 formunun açık sıfır uzantısı çoğaltır. HW uygulamasını merak ediyorum ...
Peter Cordes

2
@HansPassant, döngüsel referans başlar.
kchoi

Yanıtlar:


98

AMD değilim ya da onlar adına konuşmuyorum, ama aynı şekilde yapardım. Çünkü yüksek yarının sıfırlanması, CPU'nun beklemesi gereken önceki değere bağımlılık yaratmaz. Kayıt yeniden adlandırma bu şekilde yapılmadığı takdirde mekanizma esas mağlup olacaktır.

Bu şekilde, her zaman açıkça bağımlılıkları kırmak zorunda kalmadan, 64 bit modunda 32 bit değerleri kullanarak hızlı kod yazabilirsiniz. Bu davranış olmasaydı, 64-bit kipteki her bir 32-bit komut, daha önce olmuş bir şeyi beklemek zorunda kalacaktı, ancak bu yüksek kısım neredeyse hiç kullanılmayacaktı. ( int64 bit yapmak, önbellek ayak izini ve bellek bant genişliğini boşa harcar ; x86-64, 32 ve 64 bit işlenen boyutlarını en verimli şekilde destekler )

8 ve 16 bitlik işlenen boyutları için davranış gariptir. Bağımlılık çılgınlığı, şu anda 16 bit talimatlardan kaçınılmasının nedenlerinden biridir. x86-64, bunu 8 bit için 8086'dan ve 16 bit için 386'dan devraldı ve 8 ve 16 bitlik kayıtların 32 bit modunda olduğu gibi 64 bit modunda da çalışmasına karar verdi.


Ayrıca bkz. GCC neden kısmi kayıtları kullanmıyor? 8 ve 16 bitlik kısmi yazmaçlara yazma işlemlerinin (ve tam yazmacın sonraki okumalarının) gerçek CPU'lar tarafından nasıl işlendiğine dair pratik ayrıntılar için.


8
Tuhaf olduğunu sanmıyorum, bence çok fazla kırılmak istemediler ve eski davranışları orada tuttular.
Alexey Frunze

5
@Alex, 32bit modunu başlattıklarında, yüksek kısım için eski bir davranış yoktu. Önceleri yüksek kısım yoktu .. Tabii ondan sonra artık değiştirilemezdi.
harold

1
16 bitlik işlenenlerden bahsediyordum, bu durumda neden en üstteki bitler sıfırlanmaz. 64 bit olmayan modlarda yoklar. Ve bu da 64 bit modunda tutulur.
Alexey Frunze

3
"16 bit komutlar için davranış tuhaftır" ifadenizi "64 bit modda 16 bit işlenenlerle sıfır genişlemenin olmaması garip" olarak yorumladım. Bu nedenle, daha iyi uyumluluk için 64 bit modunda aynı şekilde tutulmasıyla ilgili yorumlarım.
Alexey Frunze

8
@Alex oh anladım. Tamam. Bu açıdan garip olduğunu düşünmüyorum. "Geriye dönüp baktığımda, belki o kadar da iyi bir fikir değildi" bakış açısıyla. Sanırım daha net olmalıydım :)
harold

9

Sadece talimatlarda ve talimat setinde yer tasarrufu sağlar. Mevcut (32 bit) talimatları kullanarak küçük anlık değerleri 64 bitlik bir kayda taşıyabilirsiniz.

Ayrıca, sizi MOV RAX, 42ne zaman MOV EAX, 42yeniden kullanılabileceği için 8 bayt değerini kodlamak zorunda kalmaktan kurtarır .

Bu optimizasyon 8 ve 16 bit operasyonlar için o kadar önemli değildir (çünkü daha küçüktürler) ve oradaki kuralları değiştirmek eski kodu da bozacaktır.


7
Eğer bu doğruysa, 0 genişlemesi yerine işaret-genişletmesi daha mantıklı olmaz mıydı?
Damien_The_Unbeliever

16
İşaret uzantısı, donanımda bile daha yavaştır. Sıfır uzatma, hesaplamanın alt yarıyı ürettiği her ne olursa olsun paralel olarak yapılabilir, ancak alt yarının hesaplanmasına kadar (en azından işareti) işaret uzantısı yapılamaz.
Jerry Coffin

13
Bir başka ilgili numara kullanmaktır XOR EAX, EAXçünkü XOR RAX, RAXbir REX önekine ihtiyaç duyar.
Neil

3
@Nubok: Elbette, hemen bir argüman alan movzx / movsx kodlamasını ekleyebilirlerdi. Çoğu zaman üst bitlerin sıfırlanması daha uygundur, böylece bir değeri bir dizi indeksi olarak kullanabilirsiniz (çünkü etkili bir adreste tüm kayıtların aynı boyutta olması gerekir: [rsi + edx]izin verilmez). Elbette yanlış bağımlılıklardan / kısmi kayıt duraklamalarından kaçınmak (diğer cevap) bir başka önemli nedendir.
Peter Cordes

4
ve oradaki kuralları değiştirmek eski kodu da bozacaktır. Eski kod 64 bit kipte zaten çalıştırılamaz (örneğin 1 baytlık inc / dec REX önekleridir); bu alakasız. Sebebi değil x86 siğilleri temizlemeye böylece daha az talimatları farklı moda göre kod çözme zorunda uzun modu ve COMPAT / eski modları arasında daha az fark olduğunu. AMD, AMD64'ün bunu yakalayacağını bilmiyordu ve maalesef çok muhafazakardı, bu yüzden desteklemek için daha az transistör gerekiyordu. Uzun vadede, derleyiciler ve insanlar 64-bit kipinde hangi şeylerin farklı çalıştığını hatırlamak zorunda kalsaydı iyi olurdu.
Peter Cordes

1

Sıfır 64 bite genişletmeden, bu, bir komut okumasının işlenen raxiçin 2 bağımlılığa sahip olacağı anlamına gelir rax(ona yazan eaxkomut ve ondan raxönce yazan komut ), bu şu anlama gelir: 1) ROB için girişler olmalıdır. tek bir işlenen için birden fazla bağımlılık, bu da ROB'nin daha fazla mantık ve transistör gerektireceği ve daha fazla yer kaplayacağı anlamına gelir ve yürütme, yürütülmesi uzun sürebilecek gereksiz bir ikinci bağımlılık için beklemenin daha yavaş olacağı anlamına gelir; veya alternatif olarak 2), tahmin ettiğim gibi 16 bitlik komutlarla olur, tahsis aşaması muhtemelen durur (yani, eğer RAT bir axyazma için aktif bir tahsise sahipse ve bir eaxokuma görünürse, axyazma emekli olana kadar durur).

mov rdx, 1
mov rax, 6
imul rax, rdx
mov rbx, rax
mov eax, 7 //retires before add rax, 6
mov rdx, rax // has to wait for both imul rax, rdx and mov eax, 7 to finish before dispatch to the execution units, even though the higher order bits are identical anyway

Sıfır genişletmenin tek yararı, daha yüksek sıralı bitlerin raxdahil edilmesini sağlamaktır, örneğin, orijinal olarak 0xffffffffffffff içeriyorsa, sonuç 0xffffffff00000007 olacaktır, ancak ISA'nın bu garantiyi böyle bir masrafla vermesi için çok az neden vardır ve Sıfır uzantının faydasının aslında daha fazla gerekli olması daha olasıdır, bu nedenle fazladan kod satırından tasarruf sağlar mov rax, 0. Her zaman sıfırın 64 bite genişletileceğini garanti ederek, derleyiciler bu aksiyomu akılda tutarak çalışabilirler mov rdx, rax, raxyalnızca tek bağımlılığını beklemek zorundadır, yani yürütmeye daha hızlı başlayıp emekli olabilir ve yürütme birimlerini serbest bırakır. Ayrıca, bir REX baytı gerektirmeden xor eax, eaxsıfır gibi daha verimli sıfır deyimlere izin verir rax.


Skylake'deki kısmi bayraklar, en azından SPAZO'lardan herhangi birine kıyasla CF için ayrı girdilere sahip olarak çalışır. (Yani cmovbe2 uops ama cmovb1). Ancak kısmi yazmacı yeniden adlandıran hiçbir CPU bunu sizin önerdiğiniz şekilde yapmaz. Bunun yerine, kısmi bir kayıt tam reg'den ayrı olarak yeniden adlandırılırsa (yani "kirli" ise) bir birleştirme uopu eklerler. Bkz. GCC neden kısmi kayıtları kullanmıyor? ve Haswell / Skylake üzerindeki kısmi kayıtlar tam olarak nasıl performans gösteriyor? AL yazmanın RAX'e yanlış bir bağımlılığı var gibi görünüyor ve AH tutarsız
Peter Cordes

P6 ailesi CPU'lar bir birleştirme uopu (Core2 / Nehalem) eklemek için ~ 3 döngü için durdu veya daha önceki P6 ailesi (PM, PIII, PII, PPro) sadece (en az?) ~ 6 döngü kaldı. Belki de bu, 2'de önerdiğiniz gibi, tam reg değerinin kalıcı / mimari kayıt dosyasına geri yazma yoluyla kullanılabilir olmasını bekliyorum.
Peter Cordes

@PeterCordes oh, en azından kısmi bayrak stall'ları için uops birleştirmeyi biliyordum. Mantıklı ama nasıl çalıştığını bir dakikalığına unuttum; bir kez tıkladı ama not almayı unuttum
Lewis Kelsey

@PeterCordes microarchitecture.pdf: This gives a delay of 5 - 6 clocks. The reason is that a temporary register has been assigned to AL to make it independent of AH. The execution unit has to wait until the write to AL has retired before it is possible to combine the value from AL with the value of the rest of EAXKısmi bayrak stall için de aynı olan 'birleştirme' örneğini bulamıyorum
Lewis Kelsey

Doğru, erken P6, yeniden yazmaya kadar durur. Core2 ve Nehalem önce / sonra bir birleştirme ekliyor mu? sadece ön ucu daha kısa bir süre için durduruyor. Sandybridge, duraklamadan birleştirme yapıyor. (Ancak, AH-birleştirme kendi başına bir döngüde yayınlanmak zorundadır, ancak AL birleştirme tam bir grubun parçası olabilir.) Haswell / SKL, AL'yi RAX'ten ayrı olarak yeniden adlandırmaz, bu nedenle mov al, [mem]mikro birleşik yük + ALU- birleştirme, yalnızca AH'yi yeniden adlandırır ve bir AH-birleştirme uop hala tek başına sorun olur. Bu CPU'lardaki kısmi bayrak birleştirme mekanizmaları değişiklik gösterir, örneğin Core2 / Nehalem, kısmi-reg'in aksine, yalnızca kısmi işaretler için durur.
Peter Cordes
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.