与C#相比,简单循环上的F#代码性能不佳 - 为什么?

时间:2013-12-12 02:55:18

标签: c# .net performance f#

我想知道为什么我在C#和F#中显然是相同的算法之间得到如此不同的结果。

F#代码变体:

open System
{ 1I..(bigint (Int32.MaxValue / 100)) } |> Seq.sum

let mutable sum = 0I
for i in 1I..(bigint (Int32.MaxValue / 100)) do
    sum <- sum + i
sum

let sum = ref 0I
for i in 1I..(bigint (Int32.MaxValue / 100)) do
    sum := !sum + i
sum

完整的F#代码(4s):

[<EntryPoint>]
let main argv = 
    let sw = new Stopwatch()
    sw.Start()
    printfn "%A" ({ 1I..(bigint (Int32.MaxValue / 100)) } |> Seq.sum)
    sw.Stop()
    printfn "took %A" sw.Elapsed
    Console.ReadKey() |> ignore
    0

完整C#代码(22s):

static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    BigInteger sum = new BigInteger(0);
    BigInteger max = new BigInteger(Int32.MaxValue / 100);
    Console.WriteLine(max);
    for (BigInteger i = new BigInteger(1); i <= max; ++i)
    {
        sum += i;
    }
    sw.Stop();
    Console.WriteLine(sum);
    Console.WriteLine(sw.Elapsed);
    Console.ReadKey();
}

F#代码在其任何变体上花费超过22秒(我假设不同的实现会产生不同的运行时间,但似乎并非如此)。另一方面,C#代码似乎更快。两者都产生相同的最终总和结果,所以我猜算法是等价的。我仔细检查过,F#代码似乎是用--optimize+标志编译的。

我做错了吗?

2 个答案:

答案 0 :(得分:8)

转换F#代码
{ 1I..(bigint (Int32.MaxValue / 100)) } |> Seq.sum;;
Real: 00:00:14.014, CPU: 00:00:14.196, GC gen0: 1743, gen1: 0

let mutable t = 1I
let mutable res = 0I
let max = bigint (Int32.MaxValue / 100)
while t < max do             
    res <- res + t
    t <- t + 1I;;
Real: 00:00:05.379, CPU: 00:00:05.450, GC gen0: 748, gen1: 0

接近三倍的速度,也更接近原始的C#代码。

最可能的原因是{...}for i in ...都会创建一个虚拟seq。通过删除它,您可以避免seq开销。

修改

由于某些原因,F#为此代码生成了大量的IL,并使用了一个非常奇怪的比较。

如果我们明确强制进行比较,速度加倍(这有点荒谬)

这段代码与C#的速度几乎完全相同(单声道)。

let mutable t = 1I
let mutable res = 0I
let max = (bigint (Int32.MaxValue / 100));;
while System.Numerics.BigInteger.op_GreaterThan(max,t) do             
     res <- res + t;t<-System.Numerics.BigInteger.op_Increment(t) 
printfn "%A" res

但是不必要地冗长。

我可能会提交一个编译错误。

答案 1 :(得分:2)

这是我能想到的最快/最短的功能版本 - 它通过使用一系列整数来欺骗。它大约和John Palmer在Mono上的版本一样快。

{1..(System.Int32.MaxValue/100)} |> Seq.sumBy (fun x -> bigint(x)) |> printfn "%A"

我还制作了John Palmer所做的功能版本,但有一个例外,它包含总和中的最大值以匹配上述基于序列的版本:

let rec sum (cnt:bigint) (acc:bigint) (max:bigint) =
    if bigint.op_LessThanOrEqual(cnt,max) then
        sum (bigint.op_Increment(cnt)) (acc+cnt) max
    else
        acc

sum 1I 0I (bigint (System.Int32.MaxValue / 100)) |> printfn "%A"