2015-02-13 9 views
7

मान लीजिए मैं कुछ पुनरावर्ती क्रिया है कि एक ग्राफ संरचना manipulates है:मैं सी में गहराई से रिकर्सिव फ़ंक्शन के स्टैक फ्रेम को कैसे कम कर सकता हूं?

typedef struct Node { 
    Data data; 
    size_t visited; 
    size_t num_neighbors; 
    struct Node *neighbors[]; 
} Node; 

void doSomethingWithNode(Node *node) 
{ 
    if ((!node) || node->visited) 
     return; 
    node->visited = 1; 
    /* Do something with node->data */ 
    size_t /* some local variables */; 
    size_t i; 
    for (i = 0; i < node->num_neighbors; i++) 
    { 
     /* Do some calculations with the local variables */ 
     if (/* something */) 
      doSomethingWithNode(node->neighbors[i]); 
    } 
} 
क्योंकि स्थानीय चर मैं पाश में उपयोग किए जाने वाले

, संकलक (gcc) एक बड़े से भी मैं-होगा की तरह ढेर फ्रेम बनाता है इस फ़ंक्शन के लिए (pushq और popq की अच्छी संख्या -O3 के साथ भी निर्देश), जो एक समस्या है, क्योंकि यह गहराई से रिकर्सिव है। चूंकि इससे कोई फर्क नहीं पड़ता कि मैं किस क्रम में नोड्स पर जाता हूं, मैं Node पॉइंटर्स के ढेर का उपयोग करने के लिए इस कोड को दोबारा कर सकता हूं, इस प्रकार प्रतिरक्षा प्रति एक सूचक को ओवरहेड को कम करता है।

  1. क्या कोई संकेत है कि मैं इस समस्या को हल करने के लिए संकलक (gcc) दे सकता हूं?
  2. यदि नहीं, तो क्या असेंबली का उपयोग किए बिना पॉइंटर्स के मेरे ढेर के लिए कॉल स्टैक का उपयोग करना संभव है?
+2

सभी पुनरावर्ती कोड भी उपयोग कर के रूप में गैर पुनरावर्ती व्यक्त किया जा सकता लूप। लिंक करते समय आप स्टैक आकार को भी बढ़ा सकते हैं (उदाहरण के लिए '-z स्टैक-साइज' [लिंकर विकल्प] (https://sourceware.org/binutils/docs/ld/Options.html#Options) का उपयोग करके) डिफ़ॉल्ट 8 एमबी (लिनक्स पर) पर्याप्त नहीं है। हालांकि मुझे वास्तव में आवश्यकता दिखाई नहीं दे रही है क्योंकि स्थानीय चर की संख्या सापेक्ष छोटी है (पाठ्यक्रम के "कुछ स्थानीय चर" के आधार पर) और बिना सरणी के। और स्थानीय चर को वास्तव में 'पुश' और' पॉप 'निर्देशों से नहीं संभाला जाता है, तो क्या आप वास्तव में सही कोड देख रहे हैं? –

+2

जीसीसी मैनुअल पेज में एक संक्षिप्त रूप के बाद मुझे एक विकल्प -फंससर्व-स्टैक दिखाई देता है। या तुमने कोशिश की? – Marian

+0

@Marian: धन्यवाद! मैं इसे आजमाऊंगा। – Matt

उत्तर

2

क्या आप गणना अपने स्वयं के, गैर-पुनरावर्ती कार्य में डाल सकते हैं? इस तरह जब आप रिकर्सिव कॉल करते हैं तो सभी अस्थायी चर के लिए ढेर नहीं होगा।

अद्यतन: स्थानीय चर में कम से कम कुछ डेटा रिकर्सन के लिए आवश्यक है जैसे दिखता है। स्टैक पर स्पष्ट रूप से आवंटित करने के लिए आप alloca का उपयोग कर सकते हैं।

+0

नहीं, उनके मूल्यों को लूप के पुनरावृत्तियों पर संरक्षित करने की आवश्यकता है। – Matt

+0

दो लूप हो सकता है? एक रिकर्सिव, एक नहीं? 'i' को स्टैक पर जरूरी है क्योंकि यह रिकर्सन की स्थिति के लिए आवश्यक है। क्या इस तरह से कोई अन्य चर आवश्यक है? – Arkadiy

+0

ऐसा करने के लिए, मुझे रिकर्सिव कॉल पर जाने के लिए पॉइंटर्स की एक सूची बनाए रखने की आवश्यकता होगी। उस बिंदु पर, मैं रिकर्सन के बजाय लूप का भी उपयोग कर सकता हूं, और यही वही है जो मैं नंबर 2 में पूछ रहा हूं। – Matt

1

समस्या को हल करने के लिए आप संकलक से क्या अपेक्षा करेंगे?

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

विफल होने के कारण, आप संभवतया पुनरावृत्ति के द्वारा कुछ स्मृति को बचा सकते हैं क्योंकि इससे वापसी पते की आवश्यकता में कटौती होती है।

1

आप नोड्स के गतिशील रूप से बढ़ते ढेर को प्रबंधित करने के लिए malloc और realloc का उपयोग कर सकते हैं। यहाँ एक ढेर के प्रबंधन के लिए "वर्ग" है:

typedef struct Stack { 
    void **pointers; 
    size_t count; 
    size_t alloc; 
} Stack; 

void Stack_new(Stack *stack) 
{ 
    stack->alloc = 10; 
    stack->count = 0; 
    stack->pointers = malloc(stack->alloc * sizeof(void*)); 
} 

void Stack_free(Stack *stack) 
{ 
    free(stack->pointers); 
    stack->pointers = null; 
} 

void Stack_push(Stack *stack, void *value) 
{ 
    if (stack->alloc < stack->count + 1) { 
     stack->alloc *= 2; 
     stack->pointers = realloc(stack->pointers, stack->alloc * sizeof(void*)); 
    } 
    stack->pointers[stack->count++] = value; 
} 

void *Stack_pop(Stack *stack) 
{ 
    if (stack->count > 0) 
     return stack->pointers[--stack->count]; 
    return NULL; 
} 
+0

मुझे नहीं लगता कि यह रिकर्सिव फ़ंक्शन में स्थानीय चर से कैसे संबंधित है? – Lundin

+0

हाँ, मैं कर सकता हूँ। लेकिन यदि संभव हो तो कॉल स्टैक का उपयोग क्यों न करें? यही कारण है कि मैं यह पूछ रहा हूँ। – Matt

+0

@ लुंडिन: मैं रिकर्स के स्टैक का उपयोग करने से बचने के लिए, स्टैक के बाहर पॉइंटर्स के ढेर को स्टोर करने में मदद कर रहा था। लेकिन अब मैं इस मुद्दे को देखता हूं, उदा। रिकर्सन के प्रत्येक स्तर के लिए ढेर पर 10 चर। –

3

आप एक वेक्टर या एक सूची (या कुछ कतार, या शायद एक ढेर, या यहाँ तक कि कुछ मनमाने ढंग से अव्यवस्थित सेट) नोड्स के दौरा किया जा करने के लिए बनाये रख सकता है (और आप शायद पहले से देखे गए नोड्स के सेट या हैश टेबल को बनाए रखना चाहते हैं)।

तो फिर तुम एक पाश जो होने वाली दौरा कंटेनर के सामने नोड उठाता होगा, और कहा कि कंटेनर की पीठ में कुछ विज़िट नहीं किए गए नोड्स जोड़ सकते हैं ....

के बारे में wikipages पढ़ें continuation passing style और के बारे में tail calls

Deutsch Schorr वैट के लिए भी गूगल एल्गोरिथ्म, तो यह आपको कुछ विचार दे सकता है।

+0

मेरा यही मतलब था जब मैंने कहा "मैं इस कोड को एक स्टैक का उपयोग करने के लिए दोबारा कर सकता हूं"। – Matt

+0

यह अक्सर एक ढेर नहीं है, लेकिन एक कतार ... –

+0

हां, लेकिन जब से मैंने कहा, आदेश कोई फर्क नहीं पड़ता, एक ढेर का उपयोग करना आसान होगा। – Matt

0

यदि आपके पास स्थानीय चर और सरणी की अधिक संख्या है तो आप malloc का उपयोग करके स्मृति आवंटित करने का प्रयास कर सकते हैं, इसे एकल सूचक और निश्चित ऑफसेट का उपयोग करके कुशलतापूर्वक उपयोग कर सकते हैं। फ़ंक्शन से बाहर निकलने पर free स्मृति।

इस तरह आप सभी पुनरावृत्तियों के लिए ढेर को बचाएंगे और उसी ढेर (शायद) अनुभाग का पुन: उपयोग करेंगे।

1

"यह गहराई से पुनरावर्ती है" एक संकेत है कि गहन पुनरावृत्ति उन पथों में होती है जिनमें 1 से अधिक गैर-विज़िट neighbor नहीं हैं।

कोड केवल तभी भर्ती करें जब 1 से अधिक दिलचस्प पड़ोसी हों, अन्यथा सिर्फ लूप।

void doSomethingWithNode(Node *node) { 
    while (node) { 
    if (node->visited) return; 
    node->visited = 1; 
    /* Do something with node->data */ 
    size_t /* some local variables */; 
    size_t i; 
    Node *first = NULL; 
    for (i = 0; i < node->num_neighbors; i++) { 
     /* Do some calculations with the local variables */ 
     if (/* something */) { 

      // Save the first interesting node->neighbors[i] for later 
      if (first == NULL && 
       node->neighbors[i] != NULL && 
       node->neighbors[i]->visited == 0) { 
      first = node->neighbors[i]; 

     } else { 
      doSomethingWithNode(node->neighbors[i]); 
      } 
     } 
    } 
    node = first; 
    } 
} 

यह स्टैक फ्रेम को कम नहीं करता है, लेकिन केवल 1-प्लाई होने पर रिकर्सन को खत्म करता है। IOWs: जब रिकर्सन की आवश्यकता नहीं है।

प्रत्यावर्तन गहराई अब नहीं रह मूल बुरी से बुरी हालत हे (एन)

+0

यह एक महान युक्ति है, धन्यवाद! – Matt

0

मैं कई करता है, तो अन्य उत्तर सुरुचिपूर्ण नहीं और भी बहुत कुछ भूमि के ऊपर की आवश्यकता होती है लगता है के बजाय ओ (log2 (एन)) होनी चाहिए। शायद कोई अच्छा रास्ता नहीं है और किसी भी तरह से हाथ में रिकर्सन के प्रकार पर निर्भर करता है।

आपके मामले में, रिकर्सन अंत में है और केवल परिवर्तनीय मुझे अभी भी आवश्यक है। स्टैक फ्रेम को कम करने के लिए, आप अन्य स्पेस ग्लोबल स्पेस के लिए उपयोग कर सकते हैं।

static struct VARS { 
    int iSomething; 
    Data *dataptr; 
    double avg; 
} gVars; 

void doSomethingWithNode(Node *node) 
{ 
    if ((!node) || node->visited) 
     return; 
    /* Do something with node->data */ 
    /* some local variables in global space */; 
    gVars.iSomething= 1; 
    for (; node->visited < node->num_neighbors; node->visited++) 
    { 
     /* Do some calculations with the local variables */ 
     if (/* something */) 
      doSomethingWithNode(node->neighbors[node->visited]); 
    } 
} 
+0

पीएस .: क्योंकि यह अभी भी एक हैक है, यह या तो सुरुचिपूर्ण नहीं है। –

+0

मुझे नहीं लगता कि यह कैसे मदद करता है। 'मैं समस्या नहीं है। समस्या अन्य स्थानीय चर है जिन्हें मैं लूप से नहीं हटा सकता। – Matt

+0

यदि लूप में ये स्थानीय चर पुनरावृत्तियों पर जानकारी नहीं लेते हैं, तो आप उन्हें वैश्विक स्थान पर भी ले जा सकते हैं। यदि वे करते हैं, तो आप उन्हें नोड्स में ले जाने पर भी विचार कर सकते हैं। –

0

सभी स्थानीय चर जो struct locals में प्रत्यावर्तन के लिए आवश्यक नहीं कर रहे हैं और plocals-> साथ उन तक पहुँच रखो:

आप और भी अधिक को कम करने और मैं निकालना चाहते हैं, तो आप उपयोग कर सकते हैं node-> एक काउंटर के रूप में visisted । गणना को अपने स्वयं के, गैर-पुनरावर्ती कार्य (Arkadiy का उत्तर) में रखने पर लाभ, यदि आवश्यक हो, तो चर मान्य हैं और रिकर्स पर उनके मूल्य बनाए रखे हैं।

#include <stddef.h> 

struct Data { 
    char data[1]; 
}; 

typedef struct Node { 
    struct Data data; 
    size_t visited; 
    size_t num_neighbors; 
    struct Node *neighbors; 
} Node; 

struct Locals { 
    /* local variables not essential for recursion */; 
}; 
static void doSomethingWithNodeRecurse(Node *node, struct Locals *plocals) 
{ 
    if ((!node) || node->visited) 
     return; 
    node->visited = 1; 
    /* Do something with node->data */ 
    /* local variables essential for recursion */ 
    size_t i; 
    for (i = 0; i < node->num_neighbors; i++) 
    { 
     /* Do some calculations with the local variables */ 
     if (1/* something */) 
      doSomethingWithNodeRecurse(&node->neighbors[i], plocals); 
     /* Do some calculations with the local variables */ 
    } 
} 

void doSomethingWithNode(Node *node) 
{ 
    struct Locals locals; 

    doSomethingWithNodeRecurse(node, &locals); 
} 

चर अभी भी बहुत ढेर पर उन्हें आवंटित करने के लिए बहुत बड़ा कर रहे हैं, वे ढेर पर आवंटित किया जा सकता है के रूप में Vagish सुझाव:

#include <stddef.h> 
#include <stdlib.h> 

struct Data { 
    char data[1]; 
}; 

typedef struct Node { 
    struct Data data; 
    size_t visited; 
    size_t num_neighbors; 
    struct Node *neighbors; 
} Node; 

struct Locals { 
    /* local variables too big for allocation on stack */; 
}; 
void doSomethingWithNode(Node *node) 
{ 
    struct Locals *plocals; 

    if ((!node) || node->visited) 
     return; 

    /* ---> allocate the variables on the heap <--- */ 
    if ((plocals = malloc(sizeof *plocals)) == NULL) abort(); 

    node->visited = 1; 
    /* Do something with node->data */ 
    size_t i; 
    for (i = 0; i < node->num_neighbors; i++) 
    { 
     /* Do some calculations with the local variables */ 
     if (1/* something */) 
      doSomethingWithNode(&node->neighbors[i]); 
     /* Do some calculations with the local variables */ 
    } 
    /* ---> free the variables <--- */ 
    free(plocals); 
} 
संबंधित मुद्दे

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