i386 (x86-32) मशीन कोड, 8 बाइट्स (अहस्ताक्षरित के लिए 9B)
+ 1B अगर हमें b = 0इनपुट पर हैंडल करना है।
amd64 (x86-64) मशीन कोड, 9 बाइट्स (अहस्ताक्षरित के लिए 10B, या 64b पूर्णांक हस्ताक्षरित या अहस्ताक्षरित के लिए 14B 13B)
Amd64 पर अहस्ताक्षरित के लिए 10 9B कि इनपुट = 0 के साथ टूट जाता है
इनपुट 32 बिट गैर-शून्य हस्ताक्षरित पूर्णांकों में eaxऔर हैं ecx। में आउटपुट eax।
## 32bit code, signed integers: eax, ecx
08048420 <gcd0>:
8048420: 99 cdq ; shorter than xor edx,edx
8048421: f7 f9 idiv ecx
8048423: 92 xchg edx,eax ; there's a one-byte encoding for xchg eax,r32. So this is shorter but slower than a mov
8048424: 91 xchg ecx,eax ; eax = divisor(from ecx), ecx = remainder(from edx), edx = quotient(from eax) which we discard
; loop entry point if we need to handle ecx = 0
8048425: 41 inc ecx ; saves 1B vs. test/jnz in 32bit mode
8048426: e2 f8 loop 8048420 <gcd0>
08048428 <gcd0_end>:
; 8B total
; result in eax: gcd(a,0) = a
यह लूप संरचना परीक्षण-मामले में विफल रहता है जहां ecx = 0। ( शून्य से विभाजित होने पर divएक #DEहार्डवेयर निष्पादन का कारण बनता है। (लिनक्स पर, कर्नेल एक SIGFPE(फ्लोटिंग पॉइंट अपवाद) वितरित करता है )। यदि लूप एंट्री बिंदु बिंदु इससे पहले सही था inc, तो हम समस्या से बचेंगे। x86-64 संस्करण इसे संभाल सकता है। मुफ्त के लिए, नीचे देखें।
माइक शालंत का जवाब इसके लिए शुरुआती बिंदु था । मेरा लूप उसके जैसा ही काम करता है, लेकिन हस्ताक्षर किए गए पूर्णांकों के लिए क्योंकि cdqएक से एक बाइट कम है xor edx,edx। और हाँ, यह एक या दोनों इनपुट नकारात्मक के साथ सही ढंग से काम करता है। माइक का संस्करण तेजी से चलेगा और यूओपी कैश में कम जगह लेगा ( xchgइंटेल सीपीयू पर 3 यूपीएस है, और loopवास्तव में अधिकांश सीपीयू पर धीमा है ), लेकिन यह संस्करण मशीन-कोड आकार में जीतता है।
मैं पहली बार में नोटिस नहीं किया था कि प्रश्न आवश्यक अहस्ताक्षरित 32 बिट। xor edx,edxइसके बजाय वापस जाने के लिए cdqएक बाइट खर्च होगी। divके रूप में एक ही आकार है idiv, और बाकी सब कुछ वही रह सकता है ( xchgडेटा आंदोलन के लिए और inc/loopअभी भी काम करता है।)
दिलचस्प है, 64 बिट ऑपरेंड-साइज़ ( raxऔर rcx) के लिए, हस्ताक्षरित और अहस्ताक्षरित संस्करण समान आकार हैं। हस्ताक्षरित संस्करण को cqo(2B) के लिए REX उपसर्ग की आवश्यकता है , लेकिन अहस्ताक्षरित संस्करण अभी भी 2B का उपयोग कर सकता है xor edx,edx।
64 बिट कोड में, inc ecx2 बी है: एकल-बाइट inc r32और dec r32ओपकोड को आरईएक्स उपसर्ग के रूप में पुनर्निर्मित किया गया था। inc/loop64 बिट मोड में किसी भी कोड-आकार को नहीं बचाता है, इसलिए आप भी कर सकते हैं test/jnz। 64 बिट पूर्णांक पर संचालन REX उपसर्गों में प्रति निर्देश के अलावा एक loopया एक बाइट जोड़ता है, या jnz। शेष 32 शून्य (उदा gcd((2^32), (2^32 + 1))) में सभी शून्य होना संभव है , इसलिए हमें पूरे आरएक्स का परीक्षण करने की आवश्यकता है और एक बाइट को नहीं बचा सकता है test ecx,ecx। हालाँकि, धीमी jrcxzइन्सान केवल 2B है, और हम इसे लूप के शीर्ष पर रख सकते हैं ताकि ecx=0प्रवेश पर नियंत्रण हो सके :
## 64bit code, unsigned 64 integers: rax, rcx
0000000000400630 <gcd_u64>:
400630: e3 0b jrcxz 40063d <gcd_u64_end> ; handles rcx=0 on input, and smaller than test rcx,rcx/jnz
400632: 31 d2 xor edx,edx ; same length as cqo
400634: 48 f7 f1 div rcx ; REX prefixes needed on three insns
400637: 48 92 xchg rdx,rax
400639: 48 91 xchg rcx,rax
40063b: eb f3 jmp 400630 <gcd_u64>
000000000040063d <gcd_u64_end>:
## 0xD = 13 bytes of code
## result in rax: gcd(a,0) = a
32 और 64b संस्करणों के लिए गॉडबोल्ट कंपाइलर एक्सप्लोरर पर स्रोत और एसएसएम आउटपुट कोmain चलाने सहित पूर्ण रन करने योग्य परीक्षण कार्यक्रम । 32bit ( ), 64bit ( ) और x32 ABI ( ) के लिए परीक्षण किया गया और काम कर रहा है ।printf("...", gcd(atoi(argv[1]), atoi(argv[2])) ); -m32-m64-mx32
यह भी शामिल है: केवल बार-बार घटाव का उपयोग करने वाला एक संस्करण , जो कि 9B है जो अहस्ताक्षरित के लिए है, यहां तक कि x86-64 मोड के लिए भी, और इसके एक इनपुट को अनियंत्रित रजिस्टर में ले सकते हैं। हालाँकि, यह प्रविष्टि पर 0 होने के साथ इनपुट को संभाल नहीं सकता है (यह पता लगाता है कि कब subशून्य पैदा करता है, जो x - 0 कभी नहीं करता है)।
32 बिट संस्करण के लिए GNU C इनलाइन asm स्रोत (साथ संकलित gcc -m32 -masm=intel)
int gcd(int a, int b) {
asm (// ".intel_syntax noprefix\n"
// "jmp .Lentry%=\n" // Uncomment to handle div-by-zero, by entering the loop in the middle. Better: `jecxz / jmp` loop structure like the 64b version
".p2align 4\n" // align to make size-counting easier
"gcd0: cdq\n\t" // sign extend eax into edx:eax. One byte shorter than xor edx,edx
" idiv ecx\n"
" xchg eax, edx\n" // there's a one-byte encoding for xchg eax,r32. So this is shorter but slower than a mov
" xchg eax, ecx\n" // eax = divisor(ecx), ecx = remainder(edx), edx = garbage that we will clear later
".Lentry%=:\n"
" inc ecx\n" // saves 1B vs. test/jnz in 32bit mode, none in 64b mode
" loop gcd0\n"
"gcd0_end:\n"
: /* outputs */ "+a" (a), "+c"(b)
: /* inputs */ // given as read-write outputs
: /* clobbers */ "edx"
);
return a;
}
आम तौर पर मैं पूरे फ़ंक्शन को asm में लिखूंगा, लेकिन GNU C इनलाइन asm को एक स्निपेट शामिल करने का सबसे अच्छा तरीका लगता है जो हम जो भी regs चुनते हैं उसमें / आउटपुट हो सकते हैं। जैसा कि आप देख सकते हैं, GNU C इनलाइन asm सिंटैक्स asm को बदसूरत और शोर बनाता है। यह भी एक है करने के लिए वास्तव में मुश्किल रास्ता जानने के एएसएम ।
यह वास्तव में .att_syntax noprefixमोड में संकलित और काम करेगा , क्योंकि उपयोग किए गए सभी इंसन्स या तो सिंगल / नो ऑपरेंड हैं या नहीं xchg। वास्तव में एक उपयोगी अवलोकन नहीं है।