简体   繁体   English

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

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

I have noticed that seemingly equivalent code in F# and C# do not perform the same. 我注意到F#和C#中看似相同的代码不会执行相同的操作。 The F# is slower by the order of magnitude. F#的数量级更慢。 As an example I am providing my code which generates prime numbers/gives nth prime number in F# and C#. 作为一个例子,我提供的代码生成素数/给出F#和C#中的第n个素数。 My F# code is: 我的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

And C# looks like this: 而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));
    }
}

When I measure for different n I get advantage for C# of at least 7x as follows: 当我测量不同的n我获得至少7x的C#优势如下:

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

My questions: 我的问题:

  • Are these two programs equivalent? 这两个程序是否相同?
  • If yes, why aren't they compiled into a same/equivalent CLI? 如果是,为什么不将它们编译成相同/等效的CLI?
  • If not, why not? 如果没有,为什么不呢?
  • How can I/Can I improve my F# prime numbers generator to perform more similar to the C# one? 我怎样才能/我可以改进我的F#素数生成器以执行更类似于C#的生成器?
  • Generally, can I (or why can I not) always mimic C# code in F# so my F# code would perform equally fast? 通常,我(或者为什么我不能)总是模仿F#中的C#代码,所以我的F#代码表现同样快?

Edit1: I've realized that the algorithm itself can be improved by traversing only through odd and not prime numbers in isprime, making it non-recursive, but this is kind of perpendicular fact to the questions asked :) 编辑1:我已经意识到算法本身可以通过仅遍历isprime中的奇数和非素数来改进,使其成为非递归的,但这对于提出的问题是一种垂直的事实:)

This: 这个:

Are these two programs equivalent? 这两个程序是否相同?

is a bit of a philosophical question. 这是一个哲学问题。

It looks to me like the output of the C# and F# implementations of isprime will always agree for any given x , so in that sense they're equivalent. 在我看来, isprime的C#和F#实现的isprime总是会同意任何给定的x ,所以在这个意义上它们是等价的。 However, there are many differences in terms of how you've implemented them (eg Seq.unfold will create an intermediate IEnumerable<_> value, then Seq.filter will create another one, so you're generating a lot more short-lived objects and using a lot more function calls in the F# code), so it's not at all surprising that they're not equivalent in terms of the low-level instructions that are generated by the respective compilers. 但是,在你如何实现它们方面存在很多差异(例如, Seq.unfold会创建一个中间的IEnumerable<_>值,然后Seq.filter将创建另一个,所以你生成了更多的短命对象并在F#代码中使用了更多的函数调用,因此,就各个编译器生成的低级指令而言,它们并不等同,这一点都不奇怪。

If you want to, you can create F# code that's much more similar to the C# code, at the expense of being much more imperative and less idiomatic: 如果你愿意,你可以创建与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 takes about 0.6s on my machine with this implementation, compared to about 2.7s with your implementation. primes |> Seq.item 5000使用此实现在我的机器上大约需要0.6秒,相比之下,您的实现大约需要2.7秒。 I think in general the code generation for F# seq expressions is often slightly worse than that of C# iterators, so I wouldn't be surprised if the C# is still somewhat quicker to run. 我认为一般来说,F# seq表达式的代码生成通常比C#迭代器的代码生成稍差,所以如果C#仍然运行得更快,我也不会感到惊讶。 (But also note that some idioms end up being faster in F# than in C#, so it's not the case that F# is always slower - in my experience the two languages are pretty comparable overall, and I find writing F# code much more enjoyable). (但是请注意,有些成语在F#中比在C#中更快,所以F#并不总是更慢 - 根据我的经验,这两种语言总体上相当,我发现编写F#代码更加愉快)。

In any case, rather than sweating the details of how to make the F# compiler's output more closely match the C# compiler's, I'd recommend looking for algorithmic improvements instead. 在任何情况下,我不建议如何使F#编译器的输出更接近C#编译器的细节,而是建议寻找算法改进。 For example, simply placing a call to Seq.cache at the end of your original definition of primes makes primes |> Seq.item 5000 take only 0.062 seconds on my machine, which is dramatically faster than the original 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