假设我有一些操纵图结构的递归函数:
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
指针,从而减少每次迭代一个指针的开销。
gcc
)解决这个问题?答案 0 :(得分:3)
您可以维护要访问的节点的向量或列表(或某个队列,或者可能是堆栈,甚至是某些任意无序集合)(并且您可能希望维护已访问节点的集合或散列表)
然后你将有一个循环选择要访问的容器前面的节点,并可能在该容器的后面添加一些未访问的节点....
阅读关于continuation passing style和tail calls
的wikipages谷歌还提供 Deutsch Schorr Waite算法,它可以给你一些想法。
答案 1 :(得分:2)
您可以将计算放入自己的非递归函数中吗?这样,当你进行递归调用时,所有临时变量的堆栈都不会存在。
更新:看起来局部变量中至少有一些数据对于递归至关重要。您可以使用alloca在堆栈上显式分配内存。
答案 2 :(得分:1)
您希望编译器为解决问题做些什么?
您当然可以浏览您的代码,并最大限度地减少局部变量的数量,尽可能使它们尽可能清晰(例如)在可能的情况下仅使用const
分配一次,并且等等。如果可能,这个可能使编译器重用该空间。
如果做不到这一点,你可以通过迭代来节省一些内存,因为这样可以减少对返回地址的需求。
答案 3 :(得分:1)
您可以使用malloc
和realloc
来管理动态增长的节点堆栈。这是&#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);
}