Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of .Net function calling (C# F#) VS C++

Since F# 2.0 has become a part of VS2010 I take an interest in F#. I wondered what's the point of using it. I'd read a bit and I made a benchmark to measure functions calling. I have used Ackermann's function :)

C#

sealed class Program
{
    public static int ackermann(int m, int n)
    {
        if (m == 0)
            return n + 1;
        if (m > 0 && n == 0)
        {
            return ackermann(m - 1, 1);
        }
        if (m > 0 && n > 0)
        {
            return ackermann(m - 1, ackermann(m, n - 1));
        }
        return 0;
    }

    static void Main(string[] args)
    {

        Stopwatch stopWatch = new Stopwatch();

        stopWatch.Start();
        Console.WriteLine("C# ackermann(3,10) = " + Program.ackermann(3, 10));
        stopWatch.Stop();

        Console.WriteLine("Time required for execution: " + stopWatch.ElapsedMilliseconds + "ms");
        Console.ReadLine();
    }
}

C++

class Program{
public:
static inline int ackermann(int m, int n)
{
  if(m == 0)
       return n + 1;
  if (m > 0 && n == 0)
  {
      return ackermann(m - 1, 1);
  }
  if (m > 0 && n > 0)
  {
      return ackermann(m - 1, ackermann(m, n - 1));
  }
  return 0;
 }
};

int _tmain(int argc, _TCHAR* argv[])
{
 clock_t start, end;

  start = clock();
 std::cout << "CPP: ackermann(3,10) = " << Program::ackermann(3, 10) << std::endl;
 end = clock();
 std::cout << "Time required for execution: " << (end-start) << " ms." << "\n\n";
 int i;
 std::cin >> i;
 return 0;
}

F#

// Ackermann
let rec ackermann m n  =
  if m = 0 then n + 1
  elif m > 0 && n = 0 then ackermann (m - 1) 1
  elif m > 0 && n > 0 then ackermann (m - 1)  (ackermann m (n - 1))
  else 0

open System.Diagnostics;
let stopWatch = Stopwatch.StartNew()
let x = ackermann 3 10 
stopWatch.Stop();

printfn "F# ackermann(3,10) = %d"  x
printfn "Time required for execution: %f"  stopWatch.Elapsed.TotalMilliseconds

Java

public class Main 
{
 public static int ackermann(int m, int n)
 {
 if (m==0) 
   return n + 1;
if (m>0 && n==0)
{
 return ackermann(m - 1,1);
}
if (m>0 && n>0)
{
  return ackermann(m - 1,ackermann(m,n - 1));
 }
 return 0;
}

  public static void main(String[] args)
  { 
   System.out.println(Main.ackermann(3,10));
  }
}

An then
C# = 510ms
c++ = 130ms
F# = 185ms
Java = Stackoverflow :)

Is it the power of F# (except small amount of code) If we want to use .Net and gain a bit faster execution? Can I optimalize any of these codes (especially F#) ?

UPDATE. I got rid off Console.WriteLine and run the C# code without the debugger: C# = 400ms

like image 833
Lukasz Madon Avatar asked Jul 13 '10 22:07

Lukasz Madon


2 Answers

I believe that in this case, the difference between C# and F# is thanks to tail-call optimization.

A tail-call is when you have a recursive call that is done as "the last thing" in a function. In this case, F# doesn't actually generate a call instruction, but instead compiles the code into a loop (because the call is the "last thing", we can reuse the current stack frame and just jump to the beginning of the method).

In your implementation of ackermann, two of the calls are tail-calls and only one of them is not (the one where the result is passed as an argument to another ackermann call). This means that the F# version actually invokes a "call" instruction (much?) less often.

In generall, the performance profile is roughly the same as performance profile of C#. There are quite a few posts related to F# vs. C# performance here:

  • Is F# really better than C# for math?
like image 70
Tomas Petricek Avatar answered Oct 25 '22 17:10

Tomas Petricek


This is kind of function calling related since it's a common method to reduce function calls.

You can make this type of recursive function go faster by way of memoization (caching). You can also do this in C# (example.) I got 18ms.

open System.Collections.Generic

let memoize2 f =
    let cache = Dictionary<_, _>()
    fun x y ->
        if cache.ContainsKey (x, y) then 
            cache.[(x, y)]
        else 
            let res = f x y
            cache.[(x, y)] <- res
            res

// Ackermann
let rec ackermann =
    memoize2 (fun m n ->
        if m = 0 then n + 1
        elif m > 0 && n = 0 then ackermann (m - 1) 1
        elif m > 0 && n > 0 then ackermann (m - 1)  (ackermann m (n - 1))
        else 0
    )
like image 24
gradbot Avatar answered Oct 25 '22 17:10

gradbot