tags:

views:

816

answers:

5

I'm testing the timing of an algorithm that does lots of recursive calls. My program dies at about 128k recursive calls, and this takes only .05 seconds. I'd like to allow more memory to have longer timings in my analysis. I'm running linux and using gcc. Is there a system call, or environment variable, or gcc flag, or wrapper, or something?

+25  A: 

Try to organize your recursive function to have tail recursion.

That is to say, make sure the last operation of your recursive function is the recursive call. By doing this, the compiler can optimize it into simply iterations.

Tail recursion will help you because iterations will dramatically decrease the stack space used.

With tail recursion, you typically pass your value UP all the way, calculating 1 step at a time. So when the recursion is done, all that needs to be done is to return.


Example:

Convert the following code:

unsigned fact(unsigned x)
{
  if(x == 1)
    return 1;

   //--> Not tail recursive, multiply operation after the recursive call
   return fact(x-1) * x;
}

To this:

unsigned fact(unsigned x)
{
    return tail_fact(x, 1);
}

unsigned tail_fact(unsigned x, unsigned cur_val)
{
  if(x == 1)
    return cur_val;  

  return tail_fact(x-1, x * cur_val);  
}
Brian R. Bondy
thats cool. Thanks for the info.
aJ
for the first (non tail recursion) case: would it be all okay if the last line would be "return x * fact(x-1)" instead? multiply is now before the recursive call.
rpr
@rpr: No that would still be equally NOT tail recursive. Because after the recursive return, you still have a multiply operation.
Brian R. Bondy
@rpr: Basically you can know if you did tail recursion based on the base case. If you return your fully calculated value in your base case, then you probably have tail recursion.
Brian R. Bondy
It is a tail call if the final statement through any branch is return method(something). Where something can be anything, but nothing can be outside the ().
Guvante
Your tail-recursive `fact` is wrong, because it has no `return` statement. (Also, I would use `unsigned` instead of `int` to prevent errors with negative values, but it would overflow long before that happened, so it's not a big deal.)
Chris Lutz
@Chris Lutz: Thanks. Added return in front of the call inside fact. Changed int to unsigned. But as far as the overflow, although true, this is not important information as far as explaining what tail recursion is.
Brian R. Bondy
+4  A: 

You have three options:

  1. Rewrite the program to make it non-recursive. This is the best, but not always possible.

  2. Rewrite the program to use a separate stack for storing the execution state. This way you preserve the recursive nature but no longer use the system stack for storing the recursion algorithm state data.

  3. Tweak the environment to postpone the inevitable. Visual C++ has a linker setting for the stack size. Almost sure gcc has one too.

sharptooth
1, is always possible because you can just as easy maintain the stack yourself.
Jasper Bekkers
+8  A: 

There is no stack size complier option for gcc under Linux. However this text discusses how to set the stack size on Linux. using the ulimit command.

GrahamS
What about the "ld --stack=<STACK_SIZE>"? Isn't it for that?
sharptooth
worked great. ulimit -s <kilobytes>
Ian Kelling
Beware that the stack will be allocated for each thread, so a big number can imply a huge memory footprint for a multithreaded app.
David Rodríguez - dribeas
It's only a big footprint if the stacks actually grow that big. Just because you ask for it, doesn't mean it's going to get allocated an actual page somewhere until you use it
Yuliy
+1  A: 

Take a look at setrlimit():

RLIMIT_STACK
This is the maximum size of the initial thread's stack, in bytes. The implementation does not automatically grow the stack beyond this limit. If this limit is exceeded, SIGSEGV shall be generated for the thread. If the thread is blocking SIGSEGV, or the process is ignoring or catching SIGSEGV and has not made arrangements to use an alternate stack, the disposition of SIGSEGV shall be set to SIG_DFL before it is generated.

Bastien Léonard
+3  A: 

Although other answers talk about how to either avoid recursion altogether, or how to use tail recursion, or how to simply set a larger stack size, I think for completeness that it's worthwhile to consider memory usage patterns (to answer "how to allow more memory ... on lots of recursion").

Out of habit, many programmers will allocate buffers inside the recursive function, and reallocate new buffers when the function is called recursively:

int recursive_function(int x)
{
    if (1 == x)
        return 1;
    int scratchpad[100];
    ... // use scratchpad
    return recursive_function(x-1) + scratchpad[x-1];
}

Since this is a throwaway sample, I won't bother worrying about invalid input (negative values, values larger than 100) and I will assume somebody asking a question about programming either knows how to do that or is smart enough to find out.

The important point here is that scratchpad takes up 400 bytes (on a 32 bit machine, 800 bytes on a 64 bit machine) of the stack each and every time recursive_function() is called, so if recursive_function() is called recursively 100 times, then 40,000 bytes (or 80,000 bytes on a 64 bit machine) of stack space are being used for buffers, and it's very likely you can modify the function to reuse the same buffer on each call:

int recursive_function(int x, int* buffer, int buffer_length)
{
    if (1 == x)
        return 1;
    ... // use buffer (and buffer_length to avoid overflow)
    int temp_value = buffer[x-1];
    return recursive_function(x-1, buffer, buffer_length) + temp_value;
}

Of course you could instead use a std::vector, which handles some details for you to protect you against memory leaks and buffer overruns (and, for the record, keeps the data on the heap [see footnote], meaning it will likely use less stack space).

40k or even 80k may not seem like much but things can add up. If the function doesn't have many other stack-allocated variables then this can dominate stack space (that is, if it weren't for the extra space the buffers take up you may be able to call the function far more times).

This may seem obvious, but it does come up, even in nonrecursive functions. Additionally, buffers aren't always obvious as arrays. They may be strings or objects, for instance.


Footnote: STL containers, such as arrays don't necessarily put all their data on the heap. They actually take a template argument to specify the memory allocation used; it's just that the allocator they use by default puts the data on the heap. Obviously unless you specify an allocator that somehow puts the data on the stack, the end result will be the same: using STL containers will probably use less memory than using stack allocated arrays or objects.

I say "probably" because although the data is kept on the heap (or somewhere else), the container can only access that data through pointers it keeps internally, if the container is on the stack then those pointers will reside on the stack, and those pointers take up space. So a one or two element std::vector may actually take up more space on the stack than the corresponding array.

Max Lybbert
I once ran into a stack overflow on an embedded platform. Basically, due to a compiler quirk with the particular optimization settings we were using, every `ASSERT()` macro within a function created a separate stack object with a 2 KB buffer, so a function with a large number of asserts had an enormous stack frame. A single recursive function, which only recursed a couple dozen times or so, was more than enough to overflow the platform's limited 128 KB stack.
Adam Rosenfield