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 ecx
2 बी है: एकल-बाइट inc r32
और dec r32
ओपकोड को आरईएक्स उपसर्ग के रूप में पुनर्निर्मित किया गया था। inc/loop
64 बिट मोड में किसी भी कोड-आकार को नहीं बचाता है, इसलिए आप भी कर सकते हैं 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
। वास्तव में एक उपयोगी अवलोकन नहीं है।