Post

Tail Call Optimization

Tail Call Optimization

When working into low level system, optimization becomes important especially for reduce time and space complexity. One useful technique in this context is Tail Call Optimization (TCO). It allows certain recursive function to executed more efficiently by reusing the current function stack frame instead of create a new one for each call. As a result, tail-recursive functions improving performance in languages or compilers that support TCO.

1
2
3
4
int recursive(int n, int acc) {
    if (n == 0) return acc; // base case
    return recursive(n - 1, acc * n); // tail call
}

πŸ“š Call stack

A good place to understand this concept, is to look the Call Stack.

Every time a function is called, the program creates a stack frame on the call stack. This frame stores:

  • Function parameter
  • Any local variable
  • Return address, which tells the CPU where to continue execution after the function finishes

When one function calls another, a new stack frame is added to the stack. When the called function returns, the frame is removed, and execution resumes at the saved return address.

Normally, this works well. However, if too many function calls are made without returning, the call stack can grow too large and cause a stack overflow.

1
2
3
4
int factorial(int n) {
    if (n == 1) return 1;
    return n * factorial(n - 1);
}

This is most common with recursive functions, where a function repeatedly calls itself with modified arguments until it reaches a base case that stops the recursion. If the recursion becomes too deep, each call adds another stack frame, eventually exhausting the available stack space.

1
2
3
4
5
6
β†’ Error: Stack frame limit exceeded
 β†’ factorial(n)
  β†’ ...
   β†’ factorial(3)
    β†’ factorial(2)
     β†’ factorial(1)

TCO helps programmer to reduce the space complexity of stack frame from O(n) to O(1), exact details will depend on hardware architecture and operating system ABI.

β†ͺ️ Jump Instruction

Let’s see this optimization image below, I use tools Compiler Explorer for debugging.

code code

if the code is not optimize the function will use call instruction in and add to stack frame and cost more to memory. TCO help this to optimize the assembly code from using call instruction and use jump instruction instead.

optimize code optimize code

To optimize this programmer need use feature of compiler. In gcc you can use option this when build:

1
$ gcc -O2 -foptimize-sibling-calls

LLVM compiler have directive [[clang::musttail]] for you to explicitly use TCO:

1
2
3
4
5
6
int get_value(int);

int recursive(int n) {
    [[clang::musttail]]
    return get_value(n);
}

πŸ”„ Recursive calls

Tail call optimization also plays a central role in functional programming languages especially Recursive Function will change into loops.

recursive call recursive call

A good compiler transform from this:

1
2
3
4
factorial(5, 1)
 β†’ factorial(4, 5)
  β†’ factorial(3, 20)
   β†’ ...

into something equivalent to:

1
2
3
4
5
6
7
int factorial(int n, int acc) {
    while (n != 0) {
        acc *= n;
        n--;
    }
    return acc;
}
This post is licensed under CC BY 4.0 by the author.