如何在C中减少深度递归函数的堆栈帧?

时间:2015-02-13 14:10:34

标签: c gcc recursion stack callstack

假设我有一些操纵图结构的递归函数:

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)为此函数创建了一个比我更大的堆栈帧(很多pushqpopq -O3指令,这是一个问题,因为它是深度递归的。由于我访问节点的顺序并不重要,我可以重构此代码以使用一堆Node指针,从而减少每次迭代一个指针的开销。

  1. 是否有任何提示我可以给编译器(gcc)解决这个问题?
  2. 如果没有,是否可以将调用堆栈本身用于我的指针堆栈而无需求助于汇编?

8 个答案:

答案 0 :(得分:3)

您可以维护要访问的节点的向量或列表(或某个队列,或者可能是堆栈,甚至是某些任意无序集合)(并且您可能希望维护已访问节点的集合或散列表)

然后你将有一个循环选择要访问的容器前面的节点,并可能在该容器的后面添加一些未访问的节点....

阅读关于continuation passing styletail calls

的wikipages

谷歌还提供 Deutsch Schorr Waite算法,它可以给你一些想法。

答案 1 :(得分:2)

您可以将计算放入自己的非递归函数中吗?这样,当你进行递归调用时,所有临时变量的堆栈都不会存在。

更新:看起来局部变量中至少有一些数据对于递归至关重要。您可以使用alloca在堆栈上显式分配内存。

答案 2 :(得分:1)

您希望编译器为解决问题做些什么?

您当然可以浏览您的代码,并最大限度地减少局部变量的数量,尽可能使它们尽可能清晰(例如)在可能的情况下仅使用const分配一次,并且等等。如果可能,这个可能使编译器重用该空间。

如果做不到这一点,你可以通过迭代来节省一些内存,因为这样可以减少对返回地址的需求。

答案 3 :(得分:1)

您可以使用mallocrealloc来管理动态增长的节点堆栈。这是&#34;班级&#34;管理堆栈:

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;
}

答案 4 :(得分:1)

&#34;它是非常递归的#34;暗示最深的递归发生在没有超过1个未访问neighbor的路径中。

让代码只在有一个以上有趣的邻居时递归,否则只是循环。

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:不需要递归时。

递归深度现在不应再超过O(log2(n))而不是原始的最坏情况O(n)

答案 5 :(得分:0)

如果你有更多的局部变量和数组,那么你可以尝试使用malloc分配内存,使用单指针和固定偏移来操纵它。free从函数退出时的内存。

通过这种方式,您将保存堆栈并为所有迭代重用相同的堆(可能)部分。

答案 6 :(得分:0)

如果其他答案不优雅且需要很多开销,我会发现很多。可能没有好的方法,任何方式都取决于手头的递归类型。

在你的情况下,递归结束,只需要变量i。要减少堆栈帧,可以使用其他变量全局空间。

如果你想减少更多并删除i,你可以使用node-&gt; visisted作为计数器:

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]);
    }
}

答案 7 :(得分:0)

将所有对递归不重要的局部变量放入struct locals并使用plocals->访问它们。如果需要,将计算放入其自己的非递归函数(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);
}