【发布时间】:2016-06-29 03:13:47
【问题描述】:
我注意到 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 时,C# 的优势至少为 7 倍,如下所示:
- n= 100:C#=5milsec F#=64milsec
- n= 1000: C#=22milsec F#=180milsec
- n= 5000: C#=280milsec F#=2.05sec
- n=10000: C#=960milsec F#=6.95sec
我的问题:
- 这两个程序是否等效?
- 如果是,为什么不将它们编译到相同/等效的 CLI 中?
- 如果没有,为什么不呢?
- 我如何/我可以改进我的 F# 素数生成器,使其性能与 C# 更相似?
- 一般来说,我能否(或者为什么我不能)总是在 F# 中模仿 C# 代码,这样我的 F# 代码的执行速度也同样快?
Edit1:我已经意识到算法本身可以通过仅遍历 isprime 中的奇数而不是质数来改进,使其成为非递归的,但这与所提出的问题有点垂直:)
【问题讨论】:
-
一个明显的区别是 takewhile 和 forall 可能会涉及两次数据传递,这可能是灾难性的,特别是如果序列没有被缓存。
-
这是一个非常好的收获!我认为编译器会优化它,但第二个想法我发现它不会。那么结合 Seq.takeWhile 和 Seq.forall 有什么好的方法呢?
-
另外,不要使用日期时间,使用秒表
-
@JohnPalmer - 我很好奇你所说的“两次通过”是什么意思 -
primes每次调用isprime只会被迭代一次(可以通过添加一些printfs 来看出) )。
标签: c# performance f# primes