x86 असेंबली, 9 बाइट्स (प्रतिस्पर्धा में प्रवेश के लिए)
हर कोई जो उच्च स्तरीय भाषाओं में इस चुनौती का प्रयास कर रहा है, कच्चे बिट्स में हेरफेर करने का असली मज़ा गायब है । ऐसा करने के तरीकों पर बहुत सारे सूक्ष्म बदलाव हैं, यह पागल है - और सोचने में बहुत मज़ा आता है। यहाँ कुछ समाधान हैं जो मैंने 32-बिट x86 असेंबली भाषा में तैयार किए हैं।
मैं अग्रिम में क्षमा चाहता हूं कि यह विशिष्ट कोड-गोल्फ उत्तर नहीं है। मैं पुनरावृत्ति अनुकूलन (आकार के लिए) की सोचा प्रक्रिया के बारे में बहुत कुछ करने के लिए जा रहा हूँ। उम्मीद है कि यह बड़े दर्शकों के लिए दिलचस्प और शैक्षिक है, लेकिन अगर आप TL हैं, तो DR;
स्पष्ट और कुशल समाधान यह परीक्षण करना है कि क्या मूल्य विषम है या यहां तक कि (जो कम से कम महत्वपूर्ण बिट को देखकर कुशलता से किया जा सकता है), और फिर तदनुसार n + 1 या n accordingly 1 के बीच चयन करें । यह मानते हुए कि इनपुट को ECXरजिस्टर में एक पैरामीटर के रूप में पारित किया गया है , और परिणाम EAXरजिस्टर में वापस आ गया है , हमें निम्नलिखित फ़ंक्शन मिलते हैं:
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 बाइट्स)
लेकिन कोड-गोल्फ प्रयोजनों के लिए, वे LEAनिर्देश महान नहीं हैं, क्योंकि वे एनकोड करने के लिए 3 बाइट लेते हैं। एक साधारण DECरीमेक ECXबहुत छोटा होगा (केवल एक बाइट), लेकिन यह झंडे को प्रभावित करता है, इसलिए हमें कोड को व्यवस्थित करने के तरीके में थोड़ा चतुर होना होगा। हम डिक्रिप्ट पहले कर सकते हैं , और ऑड / इवन टेस्ट सेकंड में , लेकिन फिर हमें ऑड / इवन टेस्ट के परिणाम को पलटना होगा।
इसके अलावा, हम एक शाखा में सशर्त-चाल अनुदेश को बदल सकते हैं, जो कोड को और अधिक धीरे-धीरे चला सकता है (यह इस बात पर निर्भर करता है कि शाखा कितनी अनुमानित है - यदि इनपुट असमान रूप से और यहां तक कि एक शाखा को वैकल्पिक रूप से वैकल्पिक करता है; पैटर्न, यह तेज़ होगा), जो हमें एक और बाइट बचाएगा।
वास्तव में, इस संशोधन के साथ, केवल एक रजिस्टर का उपयोग करके, पूरे ऑपरेशन को इन-प्लेस किया जा सकता है। यह बहुत अच्छा है अगर आप इस कोड को कहीं और से भेज रहे हैं (और संभावना है, आप होंगे, क्योंकि यह बहुत छोटा है)।
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)
(इनलाइन: 7 बाइट्स; एक फ़ंक्शन के रूप में: 10 बाइट्स)
लेकिन क्या होगा अगर आप इसे एक समारोह बनाना चाहते हैं? कोई भी मानक कॉलिंग कन्वेंशन मापदंडों को पारित करने के लिए समान रजिस्टर का उपयोग नहीं करता है जैसा कि वह रिटर्न वैल्यू के लिए करता है, इसलिए आपको MOVफ़ंक्शन की शुरुआत या अंत में एक रजिस्टर-रजिस्टर निर्देश जोड़ना होगा । यह गति में लगभग कोई लागत नहीं है, लेकिन यह 2 बाइट्स जोड़ता है। ( RETनिर्देश भी एक बाइट जोड़ता है, और फ़ंक्शन कॉल से बनाने और वापस करने की आवश्यकता के द्वारा शुरू किया गया कुछ ओवरहेड है, जिसका अर्थ है कि यह एक उदाहरण है जहां केवल एक क्लासिक गति होने के बजाय इनलाइनिंग एक गति और आकार दोनों लाभ पैदा करता है। -for-space tradeoff।) सभी में, एक फ़ंक्शन के रूप में लिखा गया है, यह कोड 10 बाइट्स के लिए फुलाता है।
10 बाइट्स में हम और क्या कर सकते हैं? यदि हम प्रदर्शन (कम से कम, पूर्वानुमेय प्रदर्शन) के बारे में बिल्कुल परवाह करते हैं , तो उस शाखा से छुटकारा पाना अच्छा होगा। यहां एक शाखा रहित, बिट-ट्विडलिंग समाधान है जो बाइट्स में समान आकार है। मूल आधार सरल है: हम पिछले बिट को फ्लिप करने के लिए एक बिट वाइज XOR का उपयोग करते हैं, एक विषम मूल्य को एक समरूप में परिवर्तित करते हैं, और इसके विपरीत। लेकिन अजीब इनपुटों के लिए एक niggle है- जो हमें n-1 देता है , जबकि इनपुट्स के लिए भी यह हमें n + 1 देता है - हम जो चाहते हैं उसके ठीक विपरीत। तो, इसे ठीक करने के लिए, हम एक नकारात्मक मूल्य पर ऑपरेशन करते हैं, प्रभावी रूप से साइन को फ्लिप करते हैं।
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
(इनलाइन: 7 बाइट्स; एक फ़ंक्शन के रूप में: 10 बाइट्स)
बहुत सुंदर; यह देखना मुश्किल है कि इस पर कैसे सुधार किया जा सकता है। एक बात मेरी आंख को पकड़ती है, हालांकि: उन दो 2-बाइट के NEGनिर्देश। सच कहूँ तो, दो बाइट्स एक साधारण बाइट को एनकोड करने के लिए एक बाइट की तरह लगता है, लेकिन यह निर्देश सेट है जिसके साथ हमें काम करना है। क्या कोई वर्कअराउंड हैं? ज़रूर! यदि हम XOR-2 करते हैं, तो हम दूसरे NEGप्याज को एक INCपुनरावृत्ति से बदल सकते हैं :
8B C1 | mov eax, ecx
|
F7 D8 | neg eax
83 F0 FE | xor eax, -2
40 | inc eax
|
C3 | ret
(इनलाइन: 6 बाइट्स; एक फ़ंक्शन के रूप में: 9 बाइट्स)
X86 निर्देश सेट की विषमताओं में से एक बहुउद्देशीय LEAनिर्देश है , जो एक रजिस्टर-रजिस्टर चाल, एक रजिस्टर-रजिस्टर जोड़, एक निरंतर द्वारा ऑफसेट, और सभी को एक निर्देश में स्केल कर सकता है!
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 बाइट्स)
ANDनिर्देश उस निर्देश की तरह है जो TESTहमने पहले इस्तेमाल किया था, जिसमें दोनों एक बिटवाइज़-एंड करते हैं और तदनुसार झंडे सेट करते हैं, लेकिन ANDवास्तव में गंतव्य ऑपरेंड को अपडेट करता है। LEAअनुदेश तो 2 से इस मापता है, 1. द्वारा मूल इनपुट मूल्य, और decrements जोड़ता है, तो इनपुट मूल्य अजीब था, इस घटाता 1 - इसे से (2 × 0 1 = -1); यदि इनपुट मान समान था, तो इसमें 1 (2 × 1 - 1 = 1) जोड़ा जाता है।
यह कोड लिखने के लिए एक बहुत तेज़ और कुशल तरीका है, क्योंकि निष्पादन का अधिकांश भाग सामने के अंत में किया जा सकता है, लेकिन यह हमें बाइट्स के तरीके से बहुत अधिक नहीं खरीदता है, क्योंकि किसी कॉम्प्लेक्स को एन्कोड करने में बहुत सारे लगते हैं LEAअनुदेश। यह संस्करण इनलाइन उद्देश्यों के लिए भी काम नहीं करता है, क्योंकि इसके लिए आवश्यक है कि मूल इनपुट मूल्य को LEAनिर्देश के इनपुट के रूप में संरक्षित किया जाए । इसलिए इस अंतिम अनुकूलन प्रयास के साथ, हम वास्तव में पीछे की ओर चले गए हैं, यह सुझाव देते हुए कि इसे रोकने का समय आ सकता है।
इस प्रकार, अंतिम प्रतिस्पर्धी प्रविष्टि के लिए, हमारे पास एक 9-बाइट फ़ंक्शन है जो ECXरजिस्टर में इनपुट मान ( 32-बिट x86 पर एक अर्ध-मानक रजिस्टर-आधारित कॉलिंग कन्वेंशन ) लेता है , और EAXरजिस्टर में परिणाम देता है (जैसा कि) सभी x86 कॉलिंग कन्वेंशन):
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 के साथ इकट्ठा होने के लिए तैयार; C से कॉल करें:
extern int __fastcall SwapParity(int value); // MSVC
extern int __attribute__((fastcall)) SwapParity(int value); // GNU