简体   繁体   English

.Net函数调用的性能(C#F#)VS C ++

[英]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#. 由于F#2.0已经成为VS2010的一部分,我对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 :) 我用过Ackermann的函数:)

C# 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++ 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# 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 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#= 510ms
c++ = 130ms c ++ = 130ms
F# = 185ms F#= 185ms
Java = Stackoverflow :) 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? 它是F#的强大功能(除了少量代码)如果我们想使用.Net并获得更快的执行速度? Can I optimalize any of these codes (especially F#) ? 我可以优化任何这些代码(尤其是F#)吗?

UPDATE . 更新 I got rid off Console.WriteLine and run the C# code without the debugger: C# = 400ms 我摆脱了Console.WriteLine并在没有调试器的情况下运行C#代码:C#= 400ms

I believe that in this case, the difference between C# and F# is thanks to tail-call optimization. 我相信在这种情况下,C#和F#之间的区别归功于尾部调用优化。

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). 在这种情况下,F#实际上并不生成调用指令,而是将代码编译成循环(因为调用是“最后一件事”,我们可以重用当前的堆栈帧并跳转到方法的开头) 。

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). 在你的ackermann实现中,有两个调用是尾调用,而其中只有一个不调用(结果作为参数传递给另一个ackermann调用)。 This means that the F# version actually invokes a "call" instruction (much?) less often. 这意味着F#版本实际上不经常调用“调用”指令(很多?)。

In generall, the performance profile is roughly the same as performance profile of C#. 通常,性能配置文件与C#的性能配置文件大致相同。 There are quite a few posts related to F# vs. C# performance here: 这里有很多与F#和C#性能相关的帖子:

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). 您可以通过memoization(缓存)使这种类型的递归函数更快。 You can also do this in C# ( example .) I got 18ms. 您也可以在C#中执行此操作( 示例 。)我有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
    )

Not directly related to the question, but interesting enough to mention: try this version 与问题没有直接关系,但有趣的是提到:试试这个版本

let rec ackermann2 m n  =
  match m,n with
  | 0,0 -> 0
  | 0,n -> n+1
  | m,0 -> ackermann2 (m-1) 1
  | m,n -> ackermann2 (m-1) (ackermann2 m (n-1))

On my PC (VS2010, F# interactive) it's almost 50% faster than your version when calculating ackermann 3 12. 在我的电脑上(VS2010,F#interactive),它在计算ackermann 3 12时比你的版本快了近50%。

I can't quite explain why there's such a performance difference. 我无法解释为什么会有这样的性能差异。 I guess it has something to do with the F# compiler translating the match expression to a switch statement instead of a series of if statements. 我想它与F#编译器有关,它将匹配表达式转换为switch语句而不是一系列if语句。 Putting the (m=0,n=0) test first may also have helped. 首先进行(m = 0,n = 0)测试也可能有所帮助。

For F# you may want to try the inline keyword. 对于F#,您可能想尝试使用inline关键字。

Also, as the previous poster mentioned, the C# and F# versions differ due to the Console.WriteLine statements. 此外,正如上一张海报所提到的,C#和F#版本因Console.WriteLine语句而异。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM