So, this one is pretty much a known thing but is generally overlooked. There is a cost associated with each function call stack and as an application developer, we should try to find ways to avoid unnecessary calls.
Let us take a look at an example. I have written 2 versions of a Fibonacci series function in C#.
static void Main(string[] args)
{
int number = 42;
Program p = new Program();
Stopwatch sw = new Stopwatch();
sw.Start();
Console.WriteLine(p.Fibonacci(number));
sw.Stop();
Console.WriteLine($"Time taken in ms : {sw.ElapsedMilliseconds}");
sw.Restart();
Console.WriteLine(p.FibonacciV2(number));
sw.Stop();
Console.WriteLine($"Time taken in ms : {sw.ElapsedMilliseconds}");
}
public int Fibonacci(int n)
{
if(n == 0 || n == 1){
return n;
}
return Fibonacci(n-2) + Fibonacci(n-1);
}
public int FibonacciV2(int n)
{
if(n == 0 || n == 1){
return n;
}
int result = 0;
Dictionary<int, int> temp = new Dictionary<int, int>();
temp.Add(0, 0);
temp.Add(1, 1);
for(int i = 2; i <= n; i++){
result = temp[i-2] + temp[i-1];
temp[i] = result;
}
return result;
}
I compiled this code in release mode and ran 4 times to check the output for comparison purpose.
267914296
Time taken in ms : 1556
267914296
Time taken in ms : 0
267914296
Time taken in ms : 1541
267914296
Time taken in ms : 0
267914296
Time taken in ms : 1609
267914296
Time taken in ms : 0
267914296
Time taken in ms : 1656
267914296
Time taken in ms : 1
What you notice is order of magnitudes faster if you calculate Fibonacci number in an iterative way instead of a recursive way. It also shows the cost of allocation of a function stack.
If you run the same program to calculate Fibonacci number for a smaller value like 20, the difference is so small that it wouldn't matter. Here is the output.
6765
Time taken in ms : 1
6765
Time taken in ms : 0
6765
Time taken in ms : 1
6765
Time taken in ms : 0
6765
Time taken in ms : 0
6765
Time taken in ms : 0
6765
Time taken in ms : 1
6765
Time taken in ms : 0
This output highlights that increase in total time is not linearly related to depth of recursion.
So where does this fit in the world of object oriented programming where you are asked to create modular single responsibility functions. There is a definite value of modular structure as keeps the Software extensible, robust, and testable.
As long as your application is not using n layers of function calls to perform trivial tasks, you will be good as the cost is not as high. There are runtime level capabilities in each platform (Java, .NET etc.) where if you chose the right options, nothing bad will happen.
A typical example in .NET is user input validation which is a frequent and (mostly) trivial operation. You can utilize model binding, custom object validators, or model state to keep validation at the entry point instead of hiding it in a utility class which is 5 layers down.