我编写了一个简单的测试,它创建了一个变量,将其初始化为零并递增 100000000 次。
C++ 在 0.36 秒内完成。原始 C# 版本 0.33s 新版本 0.8s F# 12 秒。
我没有使用任何函数,所以默认情况下问题不在于泛型
F# 代码
open System
open System.Diagnostics
// Learn more about F# at http://fsharp.org
// See the 'F# Tutorial' project for more help.
[<EntryPoint>]
let main argv =
let N = 100000000
let mutable x = 0
let watch = new Stopwatch();
watch.Start();
for i in seq{1..N} do
x <- (x+1)
printfn "%A" x
printfn "%A" watch.Elapsed
Console.ReadLine()
|> ignore
0 // return an integer exit code
C++ 代码
#include<stdio.h>
#include<string.h>
#include<vector>
#include<iostream>
#include<time.h>
using namespace std;
int main()
{
const int N = 100000000;
int x = 0;
double start = clock();
for(int i=0;i<N;++i)
{
x = x + 1;
}
printf("%d\n",x);
printf("%.4lf\n",(clock() - start)/CLOCKS_PER_SEC);
return 0;
}
C#代码
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
namespace SpeedTestCSharp
{
class Program
{
static void Main(string[] args)
{
const int N = 100000000;
int x = 0;
Stopwatch watch = new Stopwatch();
watch.Start();
foreach(int i in Enumerable.Range(0,N))
//Originally it was for(int i=0;i<N;++i)
{
x = x + 1;
}
Console.WriteLine(x);
Console.WriteLine(watch.Elapsed);
Console.ReadLine();
}
}
}
编辑
替换 for (int i = 0; i < N; ++i)
与 foreach(int i in Enumerable.Range(0,N))
让 C# 程序运行在 0.8s 左右,但仍然比 f# 快很多
编辑
已替换 DateTime
与 StopWatch
对于 F#/C#。结果是一样的
最佳答案
这肯定是由于使用表达式而直接发生的:
for i in seq{1..N} do
在我的机器上,结果如下:
100000000
00:00:09.1500924
如果我将循环更改为:
for i in 1..N do
结果发生巨大变化:
100000000
00:00:00.1001864
为什么?
这两种方法生成的 IL 是完全不同的。第二种情况,使用 1..N
语法的编译方式与 C# for(int i=1; i<N+1; ++i)
相同。循环。
第一种情况完全不同,这个版本产生一个完整的序列,然后由 foreach 循环枚举。
使用 IEnumerables
的 C# 和 F# 版本不同之处在于它们使用不同的范围函数来生成它们。
C#版本使用System.Linq.Enumerable.RangeIterator
生成值范围,而 F# 版本使用 Microsoft.FSharp.Core.Operators.OperatorIntrinsics.RangeInt32
.我认为可以肯定地假设我们在这种特殊情况下看到的 C# 和 F# 版本之间的性能差异是这两个函数的性能特征的结果。
svick 在他的评论中指出 +
是正确的。运算符实际上是作为参数传递给 integralRangeStep
功能。
对于不平凡的情况,n <> m
这导致 F# 编译器使用 ProperIntegralRangeEnumerator
在这里找到实现:https://github.com/Microsoft/visualfsharp/blob/master/src/fsharp/FSharp.Core/prim-types.fs#L6463
let inline integralRangeStepEnumerator (zero,add,n,step,m,f) : IEnumerator<_> =
// Generates sequence z_i where z_i = f (n + i.step) while n + i.step is in region (n,m)
if n = m then
new SingletonEnumerator<_> (f n) |> enumerator
else
let up = (n < m)
let canStart = not (if up then step < zero else step > zero) // check for interval increasing, step decreasing
// generate proper increasing sequence
{ new ProperIntegralRangeEnumerator<_,_>(n,m) with
member x.CanStart = canStart
member x.Before a b = if up then (a < b) else (a > b)
member x.Equal a b = (a = b)
member x.Step a = add a step
member x.Result a = f a } |> enumerator
我们可以看到单步执行枚举器会导致调用提供的 add
函数而不是更直接、更直接的添加。
注意:所有计时都在 Release模式下运行(尾调用:开启,优化:开启)。
关于c# - 为什么这个简单的 F# 代码比 C#/C++ 版本慢 36 倍?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35378119/