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

由于F#2.0已成为VS2010的一部分,我对F#感兴趣。 我想知道使用它有什么意义。 我读了一下,我做了一个测量函数调用的基准。 我用过Ackermann的函数:)

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." <> i; return 0; } 

F#

 // Ackermann let rec ackermann mn = 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)); } } 

那么
C#= 510ms
c ++ = 130ms
F#= 185ms
Java = Stackoverflow 🙂

它是F#的强大function(除了少量代码)如果我们想使用.Net并获得更快的执行速度? 我可以优化任何这些代码(尤其是F#)吗?

更新 。 我摆脱了Console.WriteLine并在没有调试器的情况下运行C#代码:C#= 400ms

我相信在这种情况下,C#和F#之间的区别归功于尾部调用优化。

尾部调用是指您在函数中作为“最后一件事”完成的递归调用。 在这种情况下,F#实际上并不生成调用指令,而是将代码编译成循环(因为调用是“最后一件事”,我们可以重用当前的堆栈帧并跳转到方法的开头) 。

在你的ackermann实现中,有两个调用是尾调用,而其中只有一个调用不是(结果作为参数传递给另一个ackermann调用的那个)。 这意味着F#版本实际上不经常调用“调用”指令(很多?)。

通常,性能配置文件与C#的性能配置文件大致相同。 这里有很多与F#和C#性能相关的post:

  • 数学上F#真的比C#好吗?

这是一种调用相关的函数,因为它是减少函数调用的常用方法。

您可以通过memoization(缓存)使这种类型的递归函数更快。 您也可以在C#中执行此操作( 示例 。)我有18ms。

 open System.Collections.Generic let memoize2 f = let cache = Dictionary<_, _>() fun xy -> if cache.ContainsKey (x, y) then cache.[(x, y)] else let res = fxy cache.[(x, y)] <- res res // Ackermann let rec ackermann = memoize2 (fun mn -> 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 ) 

与问题没有直接关系,但有趣的是要提及:试试这个版本

 let rec ackermann2 mn = 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)) 

在我的PC上(VS2010,F#interactive),它在计算ackermann 3 12时比你的版本快了近50%。

我无法解释为什么会有这样的性能差异。 我想它与F#编译器有关,它将匹配表达式转换为switch语句而不是一系列if语句。 首先进行(m = 0,n = 0)测试也可能有所帮助。

对于F#,您可能想尝试使用inline关键字。

另外,正如上一张海报所提到的,C#和F#版本因Console.WriteLine语句而异。