tags:

views:

282

answers:

11

Hi, I am new here so apologies if I did the post in a wrong way.

I was wondering if someone could please explain why is C so slow with function calling ?

Its easy to give a shallow answer to the standard question about Recursive Fibonacci, but I would appreciate if I knew the "deeper" reason as deep as possible.

Thanks.

Edit1 : Sorry for that mistake. I misunderstood an article in Wiki.

+4  A: 

C is not slow with function calls. The overhead of calling a C function is extremely low.

I challenge you to provide evidence to support your assertion.

abelenky
Compare the optimizations a functional language does to recursive calls compared to c-compilers then yes, the functional language compilers (such as ML) are more likely to give a better optimization. Thus, ML is "faster" than recursive calls than C. However, in C you can most likely implement a recursive algorithm in a different way so that you don't need to do recursive function calls.
Simon
A: 

I'm not sure what you mean. C is basically one abstraction layer on top of CPU assembly instructions, which is pretty fast. You should clarify your question really.

C Johnson
+2  A: 

I'm not sure what you mean by "a shallow answer to the standard question about Recursive Fibonacci".

The problem with the naive recursive implementation is not that the function calls are slow, but that you make an exponentially large number of calls. By caching the results (memoization) you can reduce the number of calls, allowing the algorithm to run in linear time.

Mark Byers
+1 for mentioning memoization, if not by name.
Steven Sudit
@Steven Sudit: Fixed. :)
Mark Byers
+6  A: 

When you make a function call, your program has to put several registers on the stack, maybe push some more stuff, and mess with the stack pointer. That's about all for what can be "slow". Which is, actually, pretty fast. About 10 machine instructions on an x86_64 platform.

It's slow if your code is sparse and your functions are very small. This is the case of the Fibonacci function. However, you have to make a difference between "slow calls" and "slow algorithm": calculating the Fibonacci suite with a recursive implementation is pretty much the slowest straightforward way of doing it. There is almost as much code involved in the function body than in the function prologue and epilogue (where pushing and popping takes place).

There are cases in which calling functions will actually make your code faster overall. When you deal with large functions and your registers are crowded, the compiler may have a rough time deciding in which register to store data. However, isolating code inside a function call will simplify the compiler's task of deciding which register to use.

So, no, C calls are not slow.

zneak
+1. Though pedant in me wants to add that "putting the stuff on stack" is actually what makes the function call *relatively* slow since those are extra memory writes. And memory writes are slow. But if one hits the problem, then yes, as you correctly point out the program is poorly structured. Gosh I wish C compilers could do more and better code refactoring optimizations.
Dummy00001
Good code generators don't push things into the stack if they can avoid it; instead, they put argument values in registers and only use a single pair of call/return instructions. It is harder to "call" much faster than that.
Ira Baxter
+2  A: 

The Recursive Fibonacci is the reason, not C-language. Recursive Fibonacci is something like

int f(int i)
{
    return i < 2 ? 1 : f(i-1) + f(i-2);
}

This is the slowest algorithm to calculate Fibonacci number, and by using stack store called functions list -> make it slower.

Bang Dao
Well, I'm sure there are slower ones, but the point is that no recursive Fib calculator can ever be fast for large numbers, unless it uses memoization (which is cheating).
Steven Sudit
@Steven Sudit: Define the function to return a struct holding two consecutive Fibonacci numbers. That wouldn't really be memoization, but would make things much faster.
supercat
@supercat: Cute, but cheating even more, since that amounts to memoization with a particularly small cache.
Steven Sudit
@Steven Sudit: I don't think so. No call to the recursive (return two numbers) Fibinacci routine makes use of any computations made by any previous call, nor does it require any parameters other than 'n'. BTW, my favorite variation on the 'silly' Fibonacci routine uses a subroutine with a by-reference spot for the result. The result is simply incremented with each call; since the total number of function calls to compute F(n) is F(n), increments will suffice to compute the answer.
supercat
@supercat: I realize we're firmly in the territory of silliness, but... the reason I call the two-return fib an example of memoization is simply that, for half of the numbers generated, it doesn't have to start from scratch but instead gets to use a recorded value. Because it's only fractional memoization, I expect that it will run faster but with the same algorithmic complexity, hence it fails to qualify as "fast for large numbers". In contrast, there's one answer that's logn!
Steven Sudit
@Steven Sudit: Function F(N) produces (X,Y). If N<=1, return (1,1). Otherwise, compute F(N-1) and return (oldX+oldY, OldX). The return from each function call is defined purely in terms of the output of one recursive call. Execution time and recursion depth are both linear with N.
supercat
+1  A: 

Of all the languages out there, C is probably the fastest (unless you are an assembly language programmer). Most C function calls are 100% pure stack operations. Meaning when you call a function, what this translates too in your binary code is, the CPU pushes any parameters you pass to your function onto the stack. Afterwards, it calls the function. The function then pops your parameters. After that, it executes whatever code makes up your function. Finally, any return parameters are pushed onto the stack, then the function ends and the parameters are popped off. Stack operations on any CPU are usually faster then anything else.

If you are using a profiler or something that is saying a function call you are making is slow, then it HAS to be the code inside your function. Try posting your code here and we will see what is going on.

icemanind
Assembly is not necessarily faster than C code. Also, there isn't just one calling conventions, and some use registers to pass arguments. Which is just supporting what you said: calling functions is not slow.
zneak
Especially from a lot of assembly programmers that I've met. You'll be hard pressed (though it's far from impossible) to find an asm guy these days that can do a better job than a modern compiler optimizer.
David Lively
The thing about C is, even the best C compiler in the world can only optimize code so good. A good assembly language programmer, I belive, can duplicate a function in C and make it not only smaller, but faster. With today's processors, it may not be a huge difference in speed, but assembly will come out ahead every time I think, even if it is just a fraction faster.
icemanind
@zneak: For function calls, it *could* be IF the ASM passes all parameters via registers. Even a C `_fastcall` can't use *all* the registers.
Steven Sudit
@icemanind: Not necessarily. Compilers can optimize in terms of pipelines and parallelism in a way that mere mortals cannot. These days, it's often best to program it in C, look at the ASM output and tweak the C to improve the ASM.
Steven Sudit
@Steven Sudit: the <strike>IA64</strike> AMD64 calling convention uses 8 64-bit registers to pass integers and small structures and a few (don't remember how many) SSE registers to pass real arguments. Only when this is not enough or if you pass by copy structures larger than 64 bits will the stack be used to push and pop arguments. Needless to say, it's not very typical.
zneak
"The x64 calling convention (for long mode on x86-64) takes advantage of additional register space in the AMD64/Intel 64 platform. The registers RCX, RDX, R8, R9 are used for integer and pointer arguments (in that order left to right), and XMM0, XMM1, XMM2, XMM3 are used for floating point arguments. Additional arguments are pushed onto the stack (left to right). Integer return values (similar to x86) are returned in RAX if 64 bits or less. Floating point return values are returned in XMM0. Parameters less than 64 bits long are not zero extended; the high bits contain garbage."
Steven Sudit
More at http://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention
Steven Sudit
@zneak: Keep in mind that allowing more registers to be used doesn't speed things up past a point. This is because, in order to use a register, the caller has to `PUSH` the current value so that it's preserved.
Steven Sudit
@Steven Sudit Sorry, it was 6 registers instead of 8. They use RDI and RSI in addition to the ones you've mentioned. Also, they use xmm0 to xmm7 to pass real arguments. See http://www.x86-64.org/documentation/abi-0.99.pdf
zneak
+6  A: 

Based on the additional information you posted in the comment, it seems that what is confusing you is this sentence:

"In languages (such as C and Java) that favor iterative looping constructs, there is usually significant time and space cost associated with recursive programs, due to the overhead required to manage the stack and the relative slowness of function calls;"

In the context of a recursive implementation fibonacci calculations.

What this is saying is that making recursive function calls is slower than looping but this does not mean that function calls are slow in general or that function calls in C are slower than function calls in other languages.

Fibbonacci generation is naturally a recursive algorithm, and so the most obvious and natural implementation involves many function calls, but is can also be expressed as an iteration (a loop) instead.

The fibonacci number generation algorithm in particular has a special property called tail recursion. A tail-recursive recursive function can be easily and automatically converted into an iteration, even if it is expressed as a recursive function. Some languages, particularly functional languages where recursion is very common and iteration is rare, guarantee that they will recognize this pattern and automatically transform such a recursion into an iteration "under the hood". Some optimizing C compilers will do this as well, but it is not guaranteed. In C, since iteration is both common and idiomatic, and since the tail recursive optimization is not necessarily going to be made for you by the compiler, it is a better idea to write it explicitly as an iteration to achieve the best performance.

So interpreting this quote as a comment on the speed of C function calls, relative to other languages, is comparing apples to oranges. The other languages in question are those that can take certain patterns of function calls (which happen to occur in fibbonnaci number generation) and automatically transform them into something that is faster, but is faster because it is actually not a function call at all.

Tyler McHenry
A: 

In some languages, mostly of the functional paradigm, function calls made at the end of a function body can be optimized so that the same stack frame is re-used. This can potentially save both time and space. The benefit is particularly significant when the function is both short and recursive, so that the stack overhead might otherwise dwarf the actual work being done.

The naive Fibonacci algorithm will therefore run much faster with such optimization available. C does not generally perform this optimization, so its performance could suffer.

BUT, as has been stated already, the naive algorithm for the Fibonacci numbers is horrendously inefficient in the first place. A more efficient algorithm will run much faster, whether in C or another language. Other Fibonacci algorithms probably will not see nearly the same benefit from the optimization in question.

So in a nutshell, there are certain optimizations that C does not generally support that could result in significant performance gains in certain situations, but for the most part, in those situations, you could realize equivalent or greater performance gains by using a slightly different algorithm.

Thom Smith
A: 

I agree with Mark Byers, since you mentioned the recursive Fibonacci. Try adding a printf, so that a message is printed each time you do an addition. You will see that the recursive Fibonacci is doing a lot more additions that it may appear at first glance.

gnibbler
A: 

What the article is talking about is the difference between recursion and iteration.

This is under the topic called algorithm analysis in computer science.

Suppose I write the fibonacci function and it looks something like this:

//finds the nth fibonacci
int rec_fib(n) {
 if(n == 1)
    return 1;
 else if (n == 2)
    return 1;
 else 
   return fib(n-1) + fib(n - 2)
}

Which, if you write it out on paper (I recommend this), you will see this pyramid-looking shape emerge.

It's taking A Whole Lotta Calls to get the job done.

However, there is another way to write fibonacci (there are several others too)

int fib(int n) //this one taken from scriptol.com, since it takes more thought to write it out.
{
  int first = 0, second = 1;

  int tmp;
  while (n--)
    {
      tmp = first+second;
      first = second;
      second = tmp;
    }
  return first;
}

This one only takes the length of time that is directly proportional to n,instead of the big pyramid shape you saw earlier that grew out in two dimensions.

With algorithm analysis you can determine exactly the speed of growth in terms of run-time vs. size of n of these two functions.

Also, some recursive algorithms are fast(or can be tricked into being faster). It depends on the algorithm - which is why algorithm analysis is important and useful.

Does that make sense?

Paul Nathan
yes it does. Thanks for the answer. Truth is I have already solved Fib problem recursive,iterative and with memory memoization, but I just want to find out more deep stuff. It wont be good just saying "It fills the stack" when they ask me. would it ? :)
Muggen
@Muggen: Dig up a report that walks you through algorithm analysis of the naive recursive fib implementation; do an analysis of the iterative implementation - that is about as deep as *I* can think of.
Paul Nathan
+3  A: 

There are a couple of reasons C can be slower than some other languages for a job like computing Fibonacci numbers recursively. Neither really has anything to do with slow function calls though.

In quite a few functional languages (and languages where a more or less functional style is common), recursion (often very deep recursion) is quite common. To keep speed reasonable, many implementations of such languages do a fair amount of work optimizing recursive calls to (among other things) turn them into iteration when possible.

Quite a few also "memoize" results from previous calls -- i.e., they keep track of the results from a function for a number of values that have been passed recently. When/if the same value is passed again, they can simply return the appropriate value without re-calculating it.

It should be noted, however, that the optimization here isn't really faster function calls -- it's avoiding (often many) function calls.

Jerry Coffin