2009-10-05 7 views
7

में स्टैक ओवरफ़्लो प्राप्त करने से बचने के लिए मैं वेब पर और इस साइट पर कुछ घंटों के लिए इस प्रश्न का उत्तर ढूंढने का प्रयास कर रहा हूं, और मैं काफी नहीं हूं क्या आप वहां मौजूद हैं।मैं स्टैक आकार को बदलने से कैसे बच सकता हूं और सी #

मैं समझता हूं कि .NET ऐप्स को 1 एमबी आवंटित करता है, और स्टैक आकार को मजबूर करने के बजाय रिकोडिंग द्वारा स्टैक ओवरफ़्लो से बचना सर्वोत्तम होता है।

मैं एक "सबसे छोटा रास्ता" ऐप पर काम कर रहा हूं जो लगभग 3000 नोड्स तक बढ़िया काम करता है, जिस बिंदु पर यह बहती है। यहाँ विधि है कि समस्याओं का कारण बनता है:

public Dictionary<int, int> edges = new Dictionary<int, int>(); 

ग्राफ [] है:

private Dictionary<int, Node> graph = new Dictonary<int, Node>(); 

मैं opimize करने की कोशिश की है

public void findShortestPath(int current, int end, int currentCost) 
    { 
     if (!weight.ContainsKey(current)) 
     { 
      weight.Add(current, currentCost); 
     } 
     Node currentNode = graph[current]; 
     var sortedEdges = (from entry in currentNode.edges orderby entry.Value ascending select entry); 
     foreach (KeyValuePair<int, int> nextNode in sortedEdges) 
     { 
      if (!visited.ContainsKey(nextNode.Key) || !visited[nextNode.Key]) 
      { 
       int nextNodeCost = currentCost + nextNode.Value; 
       if (!weight.ContainsKey(nextNode.Key)) 
       { 
        weight.Add(nextNode.Key, nextNodeCost); 
       } 
       else if (weight[nextNode.Key] > nextNodeCost) 
       { 
        weight[nextNode.Key] = nextNodeCost; 
       } 
      } 
     } 
     visited.Add(current, true); 
     foreach (KeyValuePair<int, int> nextNode in sortedEdges) 
     { 
      if(!visited.ContainsKey(nextNode.Key) || !visited[nextNode.Key]){ 
       findShortestPath(nextNode.Key, end, weight[nextNode.Key]); 
      } 
     } 
    }//findShortestPath 

संदर्भ के लिए, नोड वर्ग एक सदस्य है कोड ताकि वह एक पुनरावृत्ति (रिकर्सन?) से अगले की आवश्यकता के मुकाबले और अधिक सामान नहीं ले रहा है, लेकिन 100-नोड ग्राफ़ के साथ प्रत्येक नोड के साथ 1-9 किनारों के बीच होता है, यह 1 एमबी सीमा को बहुत तेज़ी से मारने जा रहा है।

वैसे भी, मैं सी # और कोड अनुकूलन के लिए नया हूं, अगर कोई मुझे कुछ पॉइंटर्स दे सकता है (not like this) मैं इसकी सराहना करता हूं।

उत्तर

9

कुछ समय पहले मैंने अपने ब्लॉग में इस समस्या की खोज की। या, बल्कि, मैंने एक संबंधित समस्या की खोज की: रिकर्सन का उपयोग किए बिना आपको बाइनरी पेड़ की गहराई कैसे मिलती है? एक पुनरावर्ती पेड़ गहराई समाधान छोटा है, लेकिन पेड़ अत्यधिक असंतुलित होने पर ढेर को उड़ाता है।

मेरी सिफारिश इस सरल समस्या को हल करने के तरीकों का अध्ययन करना है, और फिर तय करें कि उनमें से कौन सा, यदि कोई है, तो आपके थोड़ा अधिक जटिल एल्गोरिदम में अनुकूलित किया जा सकता है।

ध्यान दें कि इन लेखों में उदाहरण पूरी तरह से जेस्क्रिप्ट में दिए गए हैं। हालांकि, उन्हें सी # को अनुकूलित करना मुश्किल नहीं होना चाहिए।

यहां हम समस्या को परिभाषित करके शुरू करते हैं।

http://blogs.msdn.com/ericlippert/archive/2005/07/27/recursion-part-one-recursive-data-structures-and-functions.aspx

एक समाधान का पहला प्रयास क्लासिक तकनीक है कि आप शायद अपनाने जाएगा: एक स्पष्ट ढेर को परिभाषित; ऑपरेटिंग सिस्टम और आपके लिए ढेर को लागू करने वाले कंपाइलर पर भरोसा करने के बजाय इसका इस्तेमाल करें। इस समस्या का सामना करते समय अधिकांश लोग ऐसा करते हैं।

http://blogs.msdn.com/ericlippert/archive/2005/08/01/recursion-part-two-unrolling-a-recursive-function-with-an-explicit-stack.aspx

कि समाधान के साथ समस्या यह है कि यह एक मेस का एक सा है। हम अपना खुद का ढेर बनाने से भी आगे जा सकते हैं। हम अपनी छोटी डोमेन-विशिष्ट आभासी मशीन बना सकते हैं जिसमें उसका ढेर-आवंटित ढेर है, और उसके बाद उस मशीन को लक्षित करने वाले प्रोग्राम को लिखकर समस्या का समाधान करें! यह वास्तव में लगता है की तुलना में यह आसान है; मशीन के संचालन बेहद उच्च स्तर हो सकते हैं।

http://blogs.msdn.com/ericlippert/archive/2005/08/04/recursion-part-three-building-a-dispatch-engine.aspx

और अंत में, तुम सच में सजा के लिए एक खाऊ कर रहे हैं (या एक संकलक डेवलपर) आप पासिंग शैली निरंतरता में अपने कार्यक्रम को फिर से लिखने सकता है, जिससे एक ढेर पर सभी की आवश्यकता को समाप्त:

http://blogs.msdn.com/ericlippert/archive/2005/08/08/recursion-part-four-continuation-passing-style.aspx

http://blogs.msdn.com/ericlippert/archive/2005/08/11/recursion-part-five-more-on-cps.aspx

http://blogs.msdn.com/ericlippert/archive/2005/08/15/recursion-part-six-making-cps-work.aspx

सीपीएस सिस्टम स्टैक से अंतर्निहित स्टैक डेटा संरचना को स्थानांतरित करने और प्रतिनिधियों के समूह के बीच संबंधों में एन्कोडिंग करके ढेर पर एक विशेष रूप से चालाक तरीका है।

यहाँ प्रत्यावर्तन पर मेरे लेख के सभी कर रहे हैं:

http://blogs.msdn.com/ericlippert/archive/tags/Recursion/default.aspx

+1

या ... आप सुनिश्चित कर सकते हैं कि आपके एल्गोरिदम को सीएलआर द्वारा पूंछ रिकर्सिव रूप में परिवर्तित किया जा सके। – LBushkin

+4

सी # कभी tailcall निर्देश उत्पन्न नहीं करता है। जिटर के कुछ संस्करणों से पता चलेगा कि टेलिकल रिकर्सन का उपयोग करके एक विशेष विधि को अनुकूलित किया जा सकता है भले ही टेलकॉल निर्देश का उपयोग न किया जाए। हालांकि, हमारे द्वारा जारी किए गए अधिकांश झटके में यह अनुकूलन नहीं है, और आपको इसका भरोसा नहीं करना चाहिए। –

+3

और इसके अलावा, आपकी सलाह वास्तव में क्रियाशील नहीं है। * कैसे * वास्तव में मूल पोस्टर होना चाहिए "सुनिश्चित करें कि एल्गोरिदम को पूंछ रिकर्सिव रूप में परिवर्तित किया जा सकता है"? यह एक जटिल प्रक्रिया है जिसके लिए रनटाइम के कार्यान्वयन विवरण की गहरी समझ की आवश्यकता होती है, यह समझते हुए कि मैं 42 के निर्माण में लोगों के अलावा किसी और की अपेक्षा नहीं करता हूं। –

3

क्या आपका नोड एक संरचना या कक्षा है? यदि यह पूर्व है, तो इसे एक वर्ग बनाएं ताकि इसे ढेर के बजाय ढेर पर आवंटित किया जा सके।

+3

सभी करना होगा कि ढेर उपयोग छोटा करने, इसे समाप्त नहीं है। बड़ी डेटा संरचनाओं पर गहरी भर्ती की समस्या अभी भी मौजूद होगी। –

+0

यह सच है - पहले मैंने संख्या को केवल 3000 के रूप में देखा और सोचा कि 1 एमबी स्टैक स्पेस पर विचार करने वाला कुछ छोटा था, लेकिन यदि आप गणित करते हैं, तो यह बहुत उचित लगता है। –

16

गहरे रिकर्सिव स्टैक डाइव से बचने के लिए क्लासिक तकनीक केवल एल्गोरिदम को लिखकर और उचित सूची डेटा संरचना के साथ अपने स्वयं के "ढेर" को प्रबंधित करके रिकर्सन से बचने के लिए है। सबसे अधिक संभावना है कि आपको अपने इनपुट सेट के आकार के आकार के बारे में यहां इस दृष्टिकोण की आवश्यकता होगी।

+0

या सुनिश्चित करें कि रिकर्सिव कॉल को पूंछ रिकर्सन का उपयोग करके अनुकूलित किया जा सके। – LBushkin

7

आप रिकर्सिव होने के बजाय कोड को 'कार्य कतार' का उपयोग करने के लिए परिवर्तित कर सकते हैं। निम्नलिखित स्यूडोकोड साथ कुछ:

Queue<Task> work; 
while(work.Count != 0) 
{ 
    Task t = work.Dequeue(); 
    ... whatever 
    foreach(Task more in t.MoreTasks) 
     work.Enqueue(more); 
} 

मुझे पता है कि गुप्त है, लेकिन इसे आप करने की आवश्यकता होगी क्या की मूल अवधारणा है। चूंकि आपके वर्तमान कोड के साथ केवल 3000 नोड्स प्राप्त हो रहे हैं, इसलिए आप बिना किसी पैरामीटर के 12 ~ 15k तक पहुंचेंगे। तो आपको पूरी तरह से रिकर्सन को मारने की जरूरत है।

+0

अच्छा बिंदु। वास्तव में, ओपी से पहले गहराई के दृष्टिकोण के विपरीत आपका कोड अनिवार्य रूप से नोड्स का पहला चौथाई भाग है। –

0

मैं पहले यह सुनिश्चित कर दूंगा कि मुझे पता है कि मुझे स्टैक ओवरफ़्लो क्यों मिल रहा है। क्या यह वास्तव में रिकर्सन की वजह से है? रिकर्सिव विधि स्टैक पर ज्यादा नहीं डाल रही है। शायद यह नोड्स के भंडारण की वजह से है?

इसके अलावा, बीटीडब्ल्यू, मुझे end पैरामीटर कभी भी बदल नहीं रहा है। इससे पता चलता है कि प्रत्येक स्टैक फ्रेम पर ले जाने वाले पैरामीटर होने की आवश्यकता नहीं है।

1

मैं पहली बार यह सत्यापित होता है कि आप वास्तव में ढेर बह निकला रहे हैं: आप वास्तव में देखना एक StackOverflowException क्रम द्वारा फेंका मिलता है।

  1. अपने पुनरावर्ती क्रिया को संशोधित करें ताकि .NET रनटाइम एक tail-recursive function में परिवर्तित कर सकते हैं:

    अगर यह वास्तव में मामला है, आप कुछ ही विकल्प हैं।

  2. अपने रिकर्सिव फ़ंक्शन को संशोधित करें ताकि यह पुनरावृत्त हो और प्रबंधित स्टैक की बजाय कस्टम डेटा संरचना का उपयोग किया जा सके।

विकल्प 1 हमेशा संभव नहीं होता है, और मानता है कि पूंछ रिकर्सिव कॉल उत्पन्न करने के लिए सीएलआर का उपयोग भविष्य में स्थिर रहेगा। प्राथमिक लाभ यह है कि जब संभव हो, पूंछ पुनरावृत्ति वास्तव में स्पष्टता बलिदान के बिना रिकर्सिव एल्गोरिदम लिखने का एक सुविधाजनक तरीका है।

विकल्प 2 एक और काम है, लेकिन सीएलआर के कार्यान्वयन के प्रति संवेदनशील नहीं है और इसे किसी भी रिकर्सिव एल्गोरिदम के लिए लागू किया जा सकता है (जहां पूंछ रिकर्सन हमेशा संभव नहीं हो सकता है)। आम तौर पर, आपको कुछ लूप के पुनरावृत्तियों के बीच राज्य की जानकारी को कैप्चर और पास करने की आवश्यकता होती है, साथ ही स्टैक के स्थान (आमतौर पर एक सूची <> या स्टैक <>) डेटा संरचना को "अनलॉक" करने के तरीके के बारे में जानकारी के साथ। पुनरावृत्ति में घुसपैठ का एक तरीका continuation passing पैटर्न के माध्यम से है।

सी # पूंछ प्रत्यावर्तन के बारे में अधिक संसाधन:

Why doesn't .NET/C# optimize for tail-call recursion?

http://geekswithblogs.net/jwhitehorn/archive/2007/06/06/113060.aspx

संबंधित मुद्दे

 संबंधित मुद्दे