टेल कॉल ऑप्टिमाइज़ेशन कई भाषाओं और कंपाइलरों में मौजूद है। इस स्थिति में, संकलक प्रपत्र के एक फ़ंक्शन को पहचानता है:
int foo(n) {
...
return bar(n);
}
यहां, भाषा यह पहचानने में सक्षम है कि जो परिणाम लौटाया जा रहा है, वह किसी अन्य फ़ंक्शन का परिणाम है और एक जंप में नए स्टैक फ्रेम के साथ फ़ंक्शन कॉल को बदलें।
एहसास है कि क्लासिक factorial विधि:
int factorial(n) {
if(n == 0) return 1;
if(n == 1) return 1;
return n * factorial(n - 1);
}
है न निरीक्षण वापसी पर आवश्यक की वजह से पूंछ कॉल optimizatable। ( उदाहरण स्रोत कोड और संकलित आउटपुट )
इस टेल कॉल को अनुकूलन योग्य बनाने के लिए,
int _fact(int n, int acc) {
if(n == 1) return acc;
return _fact(n - 1, acc * n);
}
int factorial(int n) {
if(n == 0) return 1;
return _fact(n, 1);
}
इस कोड को gcc -O2 -S fact.c
(-ओ 2 को कंपाइलर में ऑप्टिमाइज़ेशन सक्षम करने के लिए आवश्यक है, लेकिन -O3 के अधिक अनुकूलन के साथ इसे पढ़ना मानव के लिए कठिन हो जाता है ...)
_fact(int, int):
cmpl $1, %edi
movl %esi, %eax
je .L2
.L3:
imull %edi, %eax
subl $1, %edi
cmpl $1, %edi
jne .L3
.L2:
rep ret
( उदाहरण स्रोत कोड और संकलित आउटपुट )
एक सेगमेंट में देख सकता है .L3
, jne
बजाय एक call
(जो एक नए स्टैक फ्रेम के साथ सबरूटीन कॉल करता है)।
कृपया ध्यान दें कि यह जावा में C. टेल कॉल ऑप्टिमाइज़ेशन के साथ किया गया था और यह JVM कार्यान्वयन पर निर्भर करता है (कहा कि, मैंने ऐसा करने वाला कोई भी नहीं देखा है, क्योंकि यह हार्ड है और आवश्यक जावा सिक्योरिटी मॉडल के स्टैक फ्रेम की आवश्यकता है। - जिसे TCO से बचा जाता है) - पूंछ-पुनरावृत्ति + जावा और पूंछ-पुनरावृत्ति + अनुकूलन ब्राउज़ करने के लिए अच्छे टैग सेट हैं। आप अन्य JVM भाषाओं का अनुकूलन पूंछ प्रत्यावर्तन बेहतर (कोशिश clojure (जिसके लिए आवश्यक करने में सक्षम हैं मिल सकता है पुनरावृत्ति होना पूंछ कॉल का अनुकूलन करने के लिए), या स्केला)।
ने कहा कि,
यह जानने में एक निश्चित आनंद है कि आपने कुछ सही लिखा है - आदर्श तरीके से कि यह किया जा सकता है।
और अब, मैं कुछ स्कॉच प्राप्त करने जा रहा हूं और कुछ जर्मन इलेक्ट्रॉनिका पर डालूंगा ...
"पुनरावर्ती एल्गोरिथ्म में स्टैक ओवरफ्लो से बचने के तरीके" के सामान्य प्रश्न के लिए ...
एक और दृष्टिकोण एक पुनरावृत्ति काउंटर को शामिल करना है। यह किसी के नियंत्रण (और खराब कोडिंग) से परे स्थितियों के कारण होने वाली अनंत छोरों का पता लगाने के लिए अधिक है।
रिकर्सन काउंटर का रूप लेता है
int foo(arg, counter) {
if(counter > RECURSION_MAX) { return -1; }
...
return foo(arg, counter + 1);
}
हर बार जब आप कॉल करते हैं, तो आप काउंटर को बढ़ाते हैं। यदि काउंटर बहुत बड़ा हो जाता है, तो आप त्रुटि करते हैं (यहां, केवल -1 की वापसी, हालांकि अन्य भाषाओं में आप एक अपवाद फेंकना पसंद कर सकते हैं)। यह विचार खराब चीजों को होने से रोक सकता है (मेमोरी त्रुटियों से) एक पुनरावृत्ति करते समय जो अपेक्षा से अधिक गहरा होता है और एक संभावित लूप होता है।
सिद्धांत रूप में, आपको इसकी आवश्यकता नहीं होनी चाहिए। व्यवहार में, मैंने खराब लिखित कोड देखा है, जो छोटी त्रुटियों और खराब कोडिंग प्रथाओं (मल्टीथ्रेडेड संगामिति के मुद्दों की बहुतायत के कारण इसे मारा है जहां कुछ विधि के बाहर कुछ बदलता है जो एक और धागा पुनरावर्ती कॉल के अनंत लूप में जाता है)।
सही एल्गोरिथ्म का उपयोग करें और सही समस्या को हल करें। Collatz अनुमान के लिए विशेष रूप से, ऐसा प्रतीत होता है कि आप इसे xkcd तरीके से हल करने का प्रयास कर रहे हैं :
आप एक संख्या में शुरू कर रहे हैं और एक पेड़ की यात्रा कर रहे हैं। यह तेजी से एक बहुत बड़े खोज स्थान की ओर जाता है। लगभग 500 चरणों में सही उत्तर परिणाम के लिए पुनरावृत्तियों की संख्या की गणना करने के लिए एक त्वरित रन। यह एक छोटे स्टैक फ्रेम के साथ पुनरावृत्ति के लिए एक मुद्दा नहीं होना चाहिए।
जबकि पुनरावर्ती समाधान जानना कोई बुरी बात नहीं है, किसी को यह भी महसूस करना चाहिए कि कई बार पुनरावृत्त समाधान बेहतर है । आ के तरीके एक सतत एक के लिए पुनरावर्ती एल्गोरिदम परिवर्तित करने का एक नंबर पर स्टैक ओवरफ़्लो पर देखा जा सकता प्रत्यावर्तन से यात्रा पर जाने के लिए रास्ता ।