簡體   English   中英

素數生成器的F#vs C#性能

[英]F# vs C# performance for prime number generator

我注意到F#和C#中看似相同的代碼不會執行相同的操作。 F#的數量級更慢。 作為一個例子,我提供的代碼生成素數/給出F#和C#中的第n個素數。 我的F#代碼是:

let rec isprime x =
primes
|> Seq.takeWhile (fun i -> i*i <= x)
|> Seq.forall (fun i -> x%i <> 0)

and primes = 
    seq {
        yield 2
        yield! (Seq.unfold (fun i -> Some(i, i+2)) 3)
                |> Seq.filter isprime
    }


let n = 1000
let start = System.DateTime.Now
printfn "%d" (primes |> Seq.nth n)
let duration = System.DateTime.Now - start
printfn "Elapsed Time: "
System.Console.WriteLine duration

而C#看起來像這樣:

class Program
{
    static bool isprime(int n)
    {
        foreach (int p in primes())
        {
            if (p * p > n)
                return true;
            if (n % p == 0)
                return false;
        }
        return true;
    }

    static IEnumerable<int> primes()
    {
        yield return 2;
        for (int i=3; ; i+=2)
        {
            if (isprime(i))
                yield return i;
        }
    }

    static void Main(string[] args)
    {
        int n = 1000;
        var pr = primes().GetEnumerator();
        DateTime start = DateTime.Now;
        for (int count=0; count<n; count++)
        {
            pr.MoveNext();
        }
        Console.WriteLine(pr.Current);
        DateTime end = DateTime.Now;
        Console.WriteLine("Duration " + (end - start));
    }
}

當我測量不同的n我獲得至少7x的C#優勢如下:

  • n = 100:C#= 5毫秒F#= 64毫秒
  • n = 1000:C#= 22毫秒F#= 180毫秒
  • n = 5000:C#= 280毫秒F#= 2.05秒
  • n = 10000:C#= 960milsec F#= 6.95sec

我的問題:

  • 這兩個程序是否相同?
  • 如果是,為什么不將它們編譯成相同/等效的CLI?
  • 如果沒有,為什么不呢?
  • 我怎樣才能/我可以改進我的F#素數生成器以執行更類似於C#的生成器?
  • 通常,我(或者為什么我不能)總是模仿F#中的C#代碼,所以我的F#代碼表現同樣快?

編輯1:我已經意識到算法本身可以通過僅遍歷isprime中的奇數和非素數來改進,使其成為非遞歸的,但這對於提出的問題是一種垂直的事實:)

這個:

這兩個程序是否相同?

這是一個哲學問題。

在我看來, isprime的C#和F#實現的isprime總是會同意任何給定的x ,所以在這個意義上它們是等價的。 但是,在你如何實現它們方面存在很多差異(例如, Seq.unfold會創建一個中間的IEnumerable<_>值,然后Seq.filter將創建另一個,所以你生成了更多的短命對象並在F#代碼中使用了更多的函數調用,因此,就各個編譯器生成的低級指令而言,它們並不等同,這一點都不奇怪。

如果你願意,你可以創建與C#代碼更相似的F#代碼,代價是更加迫切和不那么慣用:

let rec primes = 
    seq {
        yield 2
        let mutable x = 3
        while true do
            if isprime x then 
                yield x
            x <- x + 2
    }
and isprime x =
    use e = primes.GetEnumerator()
    let rec loop() =
        if e.MoveNext() then
            let p = e.Current
            if p * p > x then true
            elif x % p = 0 then false
            else loop()
        else true            
    loop()

primes |> Seq.item 5000使用此實現在我的機器上大約需要0.6秒,相比之下,您的實現大約需要2.7秒。 我認為一般來說,F# seq表達式的代碼生成通常比C#迭代器的代碼生成稍差,所以如果C#仍然運行得更快,我也不會感到驚訝。 (但是請注意,有些成語在F#中比在C#中更快,所以F#並不總是更慢 - 根據我的經驗,這兩種語言總體上相當,我發現編寫F#代碼更加愉快)。

在任何情況下,我不建議如何使F#編譯器的輸出更接近C#編譯器的細節,而是建議尋找算法改進。 例如,簡單地在原始定義primes的末尾調用Seq.cache會使primes |> Seq.item 5000在我的機器上只需0.062秒,這比原始C#快得多。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM