.Net 函数调用(C# F#)性能 VS C++
由于 F# 2.0 已成为 VS2010 的一部分,我对 F# 产生了兴趣。我想知道使用它有什么意义。我读了一点,并制定了一个基准来衡量函数调用。 我已经使用了阿克曼函数:)
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# = 185 毫秒
Java = Stackoverflow :)
如果我们想使用 .Net 并获得更快的执行速度,这是否是 F# 的强大功能(少量代码除外)?我可以优化这些代码(尤其是 F#)吗?
更新。我摆脱了 Console.WriteLine 并在没有调试器的情况下运行 C# 代码:C# = 400ms
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
我相信在这种情况下,C# 和 F# 之间的差异归功于尾部调用优化。
尾部调用是指在函数中作为“最后一件事”完成的递归调用。在这种情况下,F#实际上并没有生成调用指令,而是将代码编译成循环(因为调用是“最后一件事”,我们可以重用当前堆栈帧,只需跳转到方法的开头) 。
在您的ackermann实现中,其中两个调用是尾部调用,只有一个不是(结果作为参数传递给另一个ackermann调用) )。这意味着 F# 版本实际上调用“call”指令的频率(少得多?)更少。
一般来说,性能概况与 C# 的性能概况大致相同。这里有很多与 F# 与 C# 性能相关的帖子:
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 anotherackermann
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:
这是一种与函数调用相关的方法,因为它是减少函数调用的常用方法。
您可以通过记忆(缓存)使这种类型的递归函数运行得更快。您也可以在 C# 中执行此操作 (示例。)我得到了 18 毫秒。
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.
与问题没有直接关系,但值得一提的是:尝试这个版本
在我的电脑(VS2010,F#交互式)上计算 ackermann 3 12 时它比你的版本快了几乎 50%。
我无法完全解释为什么有这样的性能不同之处。我猜想这与 F# 编译器将匹配表达式转换为 switch 语句而不是一系列 if 语句有关。首先进行 (m=0,n=0) 测试也可能有所帮助。
Not directly related to the question, but interesting enough to mention: try this version
On my PC (VS2010, F# interactive) it's almost 50% faster than your version when calculating ackermann 3 12.
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. Putting the (m=0,n=0) test first may also have helped.
对于 F#,您可能想尝试 inline 关键字。
此外,正如前面的海报提到的,C# 和 F# 版本由于 Console.WriteLine 语句而有所不同。
For F# you may want to try the inline keyword.
Also, as the previous poster mentioned, the C# and F# versions differ due to the Console.WriteLine statements.