x86-64 मशीन कोड फ़ंक्शन, 30 बाइट्स।
@Level River St द्वारा C उत्तर के समान पुनरावृत्ति तर्क का उपयोग करता है । (अधिकतम पुनरावृत्ति गहराई = 100)
puts(3)
फ़ंक्शन को libc से उपयोग करता है , जो सामान्य निष्पादनयोग्य वैसे भी जुड़े हुए हैं। लिनक्स या OS X पर C से x86-64 सिस्टम V ABI का उपयोग करके इसे कॉल किया जा सकता है, और ऐसा कोई भी रजिस्टर्ड नहीं करता है जिसे यह नहीं माना जाता है।
objdump -drwC -Mintel
आउटपुट, स्पष्टीकरण के साथ टिप्पणी की
0000000000400340 <g>: ## wrapper function
400340: 6a 64 push 0x64
400342: 5f pop rdi ; mov edi, 100 in 3 bytes instead of 5
; tailcall f by falling into it.
0000000000400343 <f>: ## the recursive function
400343: ff cf dec edi
400345: 97 xchg edi,eax
400346: 6a 0a push 0xa
400348: 5f pop rdi ; mov edi, 10
400349: 0f 8c d1 ff ff ff jl 400320 <putchar> # conditional tailcall
; if we don't tailcall, then eax=--n = arg for next recursion depth, and edi = 10 = '\n'
40034f: 89 f9 mov ecx,edi ; loop count = the ASCII code for newline; saves us one byte
0000000000400351 <f.loop>:
400351: 50 push rax ; save local state
400352: 51 push rcx
400353: 97 xchg edi,eax ; arg goes in rdi
400354: e8 ea ff ff ff call 400343 <f>
400359: 59 pop rcx ; and restore it after recursing
40035a: 58 pop rax
40035b: e2 f4 loop 400351 <f.loop>
40035d: c3 ret
# the function ends here
000000000040035e <_start>:
0x040035e - 0x0400340 = 30 bytes
# not counted: a caller that passes argc-1 to f() instead of calling g
000000000040035e <_start>:
40035e: 8b 3c 24 mov edi,DWORD PTR [rsp]
400361: ff cf dec edi
400363: e8 db ff ff ff call 400343 <f>
400368: e8 c3 ff ff ff call 400330 <exit@plt> # flush I/O buffers, which the _exit system call (eax=60) doesn't do.
के साथ बनाया गया yasm -felf64 -Worphan-labels -gdwarf2 golf-googol.asm &&
gcc -nostartfiles -o golf-googol golf-googol.o
। मैं मूल NASM स्रोत को पोस्ट कर सकता हूं, लेकिन यह अव्यवस्था की तरह लग रहा था क्योंकि asm निर्देश सही disassembly में हैं।
putchar@plt
से कम से कम 128 बाइट्स है jl
, इसलिए मैं 6-बाइट के बजाय कूद के पास 2-बाइट छोटी कूद का उपयोग कर सकता था, लेकिन यह केवल एक छोटे से निष्पादन में सच है, एक बड़े कार्यक्रम के हिस्से के रूप में नहीं। इसलिए मुझे नहीं लगता कि मैं एफबीआईसी के पुट को लागू करने के आकार की गणना नहीं कर सकता, अगर मैं इसे हासिल करने के लिए शॉर्ट जेक एन्कोडिंग का लाभ उठाता हूं।
रिकर्सन के प्रत्येक स्तर में 24B स्टैक स्पेस (2 पुश और CALL द्वारा दिया गया वापसी पता) का उपयोग किया जाता है। हर दूसरी गहराई putchar
केवल 8 द्वारा संरेखित स्टैक के साथ कॉल करेगी , 16 नहीं, इसलिए यह एबीआई का उल्लंघन करता है। एक stdio कार्यान्वयन जो स्टैक गलती करेगा xmm रजिस्टरों को फैलाने के लिए संरेखित स्टोर का उपयोग करता है। लेकिन glibc putchar
ऐसा नहीं करता है, पूर्ण बफरिंग के साथ एक पाइप पर लिखना या लाइन बफरिंग के साथ टर्मिनल पर लिखना है। उबंटू 15.10 पर परीक्षण किया गया। यह एक डमी पुश / पॉप के साथ तय किया जा सकता है .loop
, पुनरावर्ती कॉल से पहले एक और 8 से स्टैक को ऑफसेट करने के लिए।
सबूत है कि यह नई संख्या की सही संख्या प्रिंट करता है:
# with a version that uses argc-1 (i.e. the shell's $i) instead of a fixed 100
$ for i in {0..8}; do echo -n "$i: "; ./golf-googol $(seq $i) |wc -c; done
0: 1
1: 10
2: 100
3: 1000
4: 10000
5: 100000
6: 1000000
7: 10000000
8: 100000000
... output = 10^n newlines every time.
इस का मेरा पहला संस्करण 43B था, और इसका इस्तेमाल किया गया था puts()
9 नईलाइनों (और एक 0 बाइट) के बफर पर , इसलिए पुट 10 वें स्थान पर होगा। वह रिक्रिएशन बेस-केस सी प्रेरणा के भी करीब था।
फैक्टरिंग 10 ^ 100 एक अलग तरीका शायद बफर को छोटा कर सकता है, शायद 4 नईलाइन्स तक, 5 बाइट्स बचा सकता है, लेकिन पुचर का उपयोग करना अब तक बेहतर है। इसमें केवल एक पूर्णांक arg की आवश्यकता होती है, एक सूचक की नहीं, और किसी भी बफर की नहीं। सी मानक कार्यान्वयन की अनुमति देता है जहां यह एक मैक्रो है putc(val, stdout)
, लेकिन glibc में यह एक वास्तविक फ़ंक्शन के रूप में मौजूद है जिसे आप asm से कॉल कर सकते हैं।
10 के बजाय प्रति कॉल केवल एक न्यूलाइन को प्रिंट करने का मतलब है कि हमें 10 मिनट की सुर्खियों में आने के लिए 1 से पुनरावृत्ति अधिकतम गहराई बढ़ाने की आवश्यकता है। चूंकि 99 और 100 दोनों को एक संकेत-विस्तारित 8-बिट तत्काल द्वारा दर्शाया जा सकता है,push 100
अभी भी केवल 2 बाइट्स है।
इससे भी बेहतर, 10
एक रजिस्टर में एक न्यूलाइन और लूप काउंटर दोनों के रूप में काम करता है, एक बाइट को बचाता है।
बाइट बचाने के लिए विचार
एक 32-बिट संस्करण के लिए एक बाइट को बचा सकता है dec edi
, लेकिन स्टैक-आर्ग्स कॉलिंग कन्वेंशन (पुश्तैनी जैसे पुस्तकालय कार्यों के लिए) टेल-कॉल काम को आसानी से कम कर देता है, और संभवतः अधिक स्थानों में अधिक बाइट्स की आवश्यकता होगी। मैं निजी के लिए रजिस्टर-आर्ग कन्वेंशन का उपयोग कर सकता हूं f()
, केवल इसके द्वारा बुलाया जाता है g()
, लेकिन तब मैं पुटचार्च नहीं कर सकता था (क्योंकि f () और putchar () स्टैक-आर्ग्स की एक अलग संख्या ले जाएगा)।
यह संभव होगा कि कॉलर में सेव / रिस्टोर करने के बजाय, कॉलर की स्थिति को सुरक्षित रखें। वह शायद बेकार है, हालांकि, क्योंकि यह संभवतः शाखा के प्रत्येक पक्ष में अलग से प्राप्त करने की आवश्यकता होगी, और टेलकॉलिंग के साथ संगत नहीं है। मैंने इसे आजमाया लेकिन कोई बचत नहीं मिली।
स्टैक पर एक लूप काउंटर रखने के बजाय (लूप में पुश / पॉपिंग आरसीएक्स के बजाय) या तो मदद नहीं की। यह उस संस्करण के साथ 1B बदतर था जो पुट का उपयोग करता था, और शायद इस संस्करण के साथ नुकसान भी अधिक है जो कि आरसीएक्स को अधिक सस्ते में सेट करता है।