2013-12-12 95 views
5

我想知道爲什麼我在C#和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#代碼需要比22S更在其任何變體(我假設不同的實現會產生不同的運行時間,但似乎並非如此)。另一方面,C#代碼似乎更快。兩者都產生相同的最終和結果,所以我猜算法是相同的。我再次檢查,F#代碼似乎與--optimize+標誌編譯。

我做錯了什麼?

+2

嘗試使用'來''for'循環而不是'in'。 – ildjarn

+0

@ildjarn - 帶'to'的循環需要'int'類型,但在這裏我們使用'BigInteger',所以這不起作用。 –

+0

@JohnPalmer:啊,道歉,我沒有意識到(我從字面上從未在F#中使用過'for' :-P)。 – ildjarn

回答

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 

Approxiamately三倍的速度,並且還更接近原始C#代碼。

最可能的原因是{...}for i in ...創建了一個虛擬seq。通過消除這個,你可以避免seq開銷。

EDIT

出於某種原因,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 

但是非常詳細。

我可能會提交一個關於這個的編譯器錯誤。

+0

這很奇怪,正如OP所述,所有3個F#變體在這裏產生完全相同的速度。或者我可能很需要睡眠。我將再次運行它們。 –

+0

@devouredelysium - 我正在提出一種新的替代方法,它的速度要快得多 - 所有版本在我的電腦上花費了大約15秒。 –

+0

你的算法在這裏需要8.8s。這確實比較好,但即便如此,我希望它匹配C#版本:(這只是太慢 –

1

這是我能想出的最快/最短的功能版本 - 它通過使用一系列的整數作弊。它與John Palmer在Mono上的版本差不多。

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

我還做了什麼約翰·帕爾默,它包括在總和的最大值來匹配上面的順序基於版本有一個例外做了功能版本:

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" 
相關問題