I have noticed that seemingly equivalent code in F# and C# do not perform the same. The F# is slower by the order of magnitude. As an example I am providing my code which generates prime numbers/gives nth prime number in F# and C#. My F# code is:
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:
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:
My questions:
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 :)
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. However, there are many differences in terms of how you've implemented them (e.g. 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.
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:
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. 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. (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).
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. 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#.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With