x86 Meclisi, 9 bayt (yarışmaya katılmak için)
Bu zorluğa üst düzey dilde giriş yapan herkes, çiğ bitleri işlemenin gerçek eğlencesini kaçırıyor . Bunu yapmanın yollarında çok ince varyasyonlar var, bu çılgınca ve düşünmesi çok eğlenceli. İşte 32-bit x86 assembly dilinde geliştirdiğim birkaç çözüm.
Bunun tipik kod golfü cevabı olmadığı için şimdiden özür dilerim. Yinelemeli optimizasyonun düşünce süreci (büyüklük için) hakkında çok şey değiştireceğim. Umarım, bu daha geniş bir kitleye ilginç ve eğiticidir, ancak TL; DR tipi iseniz, sonuna kadar atlarsanız kırılmayacağım.
Açık ve verimli çözüm, değerin tek mi yoksa çift mi olduğunu (en az anlamlı bite bakarak verimli bir şekilde yapılabilir) test etmek ve sonra buna göre n + 1 veya n − 1 arasında seçim yapmaktır. Girişin ECX
kayıt defterinde bir parametre olarak iletildiğini ve sonucun EAX
kayıt defterine döndürüldüğünü varsayarak , aşağıdaki işlevi elde ederiz:
F6 C1 01 | test cl, 1 ; test last bit to see if odd or even
8D 41 01 | lea eax, DWORD PTR [ecx + 1] ; set EAX to n+1 (without clobbering flags)
8D 49 FF | lea ecx, DWORD PTR [ecx - 1] ; set ECX to n-1 (without clobbering flags)
0F 44 C1 | cmovz eax, ecx ; move in different result if input was even
C3 | ret
(13 bayt)
Ancak kod-golf amaçlı bu LEA
kodlar mükemmel değildir, çünkü kodlamaları 3 byte alırlar. Basit bir DEC
rement ECX
daha kısa olacaktır (sadece bir bayt), fakat bu bayrakları etkiler, bu yüzden kodu nasıl düzenlediğimiz konusunda biraz akıllı olmamız gerekir. Biz azalmada yapabiliriz ilk ve tek / çift deney ikinci , ama sonra tek / çift testin çevirmek zorunda.
Ayrıca, koşullu taşıma komutunu bir kod olarak değiştirebiliriz, bu kodun daha yavaş çalışmasını sağlayabilir (dalın ne kadar öngörülebilir olduğuna bağlı olarak - giriş tek veya çift arasında tutarsız bir şekilde değişiyorsa, bir dal yavaş olacaktır; desen, daha hızlı olacak), ve bizi başka bir bayttan kurtaracak.
Aslında, bu revizyon ile, tüm işlem yerinde, sadece tek bir kayıt kullanılarak yapılabilir. Eğer bu kodu bir yere yazıyorsanız, bu harika bir şey (ve olasılıklar çok kısa olduğu için olabilirsiniz).
48 | dec eax ; decrement first
A8 01 | test al, 1 ; test last bit to see if odd or even
75 02 | jnz InputWasEven ; (decrement means test result is inverted)
40 | inc eax ; undo the decrement...
40 | inc eax ; ...and add 1
InputWasEven: ; (two 1-byte INCs are shorter than one 3-byte ADD with 2)
(satır içi: 7 bayt; işlev olarak: 10 bayt)
Ama ya onu bir işlev yapmak istersen? Hiçbir standart çağrı kuralı, dönüş değeri için olduğu gibi parametreleri iletmek için aynı kaydı kullanmaz; bu nedenle MOV
, işlevin başına veya sonuna bir kayıt yazma talimatı eklemeniz gerekir . Bu hemen hemen hiçbir hızda maliyeti yoktur, ancak 2 bayt ekler. ( RET
Talimat aynı zamanda bir bayt ekler ve bunun anlamı bir işlev çağrısı, gelen yapmaya gerek ve iade tarafından tanıtılan bir ek yük vardır inlining bir hız hem üreten bir örnektir ve sadece klasik bir hız olmaktan ziyade boyutu yararı, -for-space tradeoff için.) Sonuç olarak, bir işlev olarak yazılmış bu kod 10 baytlık bir süredir.
10 baytta başka ne yapabiliriz? Performansı hiç değilse (en azından tahmin edilebilir performans) önemsersek, bu daldan kurtulmak iyi olurdu. İşte bayt olarak aynı boyutta bir dalsız, bit bükme çözümü. Temel öncül basittir: son biti çevirmek için bitsel bir XOR kullanırız, tek bir değeri çift bire dönüştürür ve tam tersi olur. Ancak tek girişler var - garip girişler için, bize n-1 veren , hatta girişler için n + 1 veriyor - tam olarak istediklerimizin tam tersi. Bu yüzden, bunu düzeltmek için, işlemi negatif bir değer üzerinde yapıyoruz ve işareti etkin bir şekilde çeviriyoruz.
8B C1 | mov eax, ecx ; copy parameter (ECX) to return register (EAX)
|
F7 D8 | neg eax ; two's-complement negation
83 F0 01 | xor eax, 1 ; XOR last bit to invert odd/even
F7 D8 | neg eax ; two's-complement negation
|
C3 | ret ; return from function
(satır içi: 7 bayt; işlev olarak: 10 bayt)
Oldukça kaygan; Bunun nasıl geliştirilebileceğini görmek zor. Yine de bir şey gözüme çarpıyor: bu iki baytlık NEG
talimatlar. Açıkçası, iki bayt, basit bir olumsuzlamayı kodlamak için çok fazla bir bayt gibi görünüyor, ancak bu bizim birlikte çalışmamız gereken komut seti. Herhangi bir geçici çözüm var mı? Emin! Eğer XOR
-2'ye kadar çıkarsak , ikinci NEG
ationu bir INC
rement ile değiştirebiliriz :
8B C1 | mov eax, ecx
|
F7 D8 | neg eax
83 F0 FE | xor eax, -2
40 | inc eax
|
C3 | ret
(satır içi: 6 bayt; işlev olarak: 9 bayt)
X86 komut setinin tuhaflıklar başka biri çok amaçlı LEA
talimat bir kayıt-kayıt taşımak, bir kayıt-kayıt eklenmesini yapabilir, bir sabiti tarafından mahsup ve hepsi tek talimatında ölçekleme!
8B C1 | mov eax, ecx
83 E0 01 | and eax, 1 ; set EAX to 1 if even, or 0 if odd
8D 44 41 FF | lea eax, DWORD PTR [ecx + eax*2 - 1]
C3 | ret
(10 bayt)
AND
Talimat gibidir TEST
de o doğrultuda bir bitdüzeyi-AND ve seti bayrakları yapmak içinde, daha önce kullanılan talimat ama AND
aslında işlenen hedef günceller. Daha LEA
sonra komut bunu 2'ye ölçeklendirir, orijinal giriş değerini ekler ve 1 değerini azaltır. Giriş değeri tek ise, bu değer 1'i (2 × 0 - 1 = −1) çıkarır; eğer giriş değeri eşitse, buna 1 (2 × 1 - 1 = 1) eklenir.
Bu, kodu yazmak için çok hızlı ve etkili bir yoldur, çünkü yürütmenin çoğu ön uçta yapılabilir, ancak karmaşık bir kodun kodlanması çok zaman aldığından bizi bayt şeklinde satın almaz. LEA
talimat. Bu sürüm ayrıca orijinal giriş değerinin LEA
talimat girişi olarak korunmasını gerektirdiğinden, satır içi amaçlarla da çalışmaz . Dolayısıyla, bu son optimizasyon girişimi ile gerçekte geriye doğru gittik, durmanın zamanı gelebileceğini öne sürdük.
Bu nedenle, son yarışmaya giriş için, ECX
kayıttaki giriş değerini alan ( 32-bit x86'da yarı standart kayıt tabanlı bir çağrı kuralı) giriş yapan 9 baytlık bir fonksiyona sahibiz ve sonucu EAX
kayıtta döndürür. tüm x86 arama kuralları):
SwapParity PROC
8B C1 mov eax, ecx
F7 D8 neg eax
83 F0 FE xor eax, -2
40 inc eax
C3 ret
SwapParity ENDP
MASM ile montaja hazır; C’den şu şekilde arama yapın:
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU