【发布时间】:2011-09-22 03:15:16
【问题描述】:
我读过this other post about a F# version of this algorithm。我发现它非常优雅,并尝试将答案的一些想法结合起来。
虽然我对其进行了优化以减少检查(仅检查 6 左右的数字)并省去不必要的缓存,但它仍然非常缓慢。计算第 10,000th 个素数已经花费了 5 多分钟。使用命令式方法,我可以在不多的时间内测试所有 31 位整数。
所以我的问题是我是否遗漏了一些让这一切变得如此缓慢的东西。例如在another post 中,有人推测LazyList 可能会使用锁定。有人有想法吗?
由于 StackOverflow 的规则规定不要发布新问题作为答案,我觉得我必须为此开始一个新话题。
代码如下:
#r "FSharp.PowerPack.dll"
open Microsoft.FSharp.Collections
let squareLimit = System.Int32.MaxValue |> float32 |> sqrt |> int
let around6 = LazyList.unfold (fun (candidate, (plus, next)) ->
if candidate > System.Int32.MaxValue - plus then
None
else
Some(candidate, (candidate + plus, (next, plus)))
) (5, (2, 4))
let (|SeqCons|SeqNil|) s =
if Seq.isEmpty s then SeqNil
else SeqCons(Seq.head s, Seq.skip 1 s)
let rec lazyDifference l1 l2 =
if Seq.isEmpty l2 then l1 else
match l1, l2 with
| LazyList.Cons(x, xs), SeqCons(y, ys) ->
if x < y then
LazyList.consDelayed x (fun () -> lazyDifference xs l2)
elif x = y then
lazyDifference xs ys
else
lazyDifference l1 ys
| _ -> LazyList.empty
let lazyPrimes =
let rec loop = function
| LazyList.Cons(p, xs) as ll ->
if p > squareLimit then
ll
else
let increment = p <<< 1
let square = p * p
let remaining = lazyDifference xs {square..increment..System.Int32.MaxValue}
LazyList.consDelayed p (fun () -> loop remaining)
| _ -> LazyList.empty
loop (LazyList.cons 2 (LazyList.cons 3 around6))
【问题讨论】:
-
缓慢是在你的
(|SeqCons|SeqNil|)活动模式中,大约需要 O(n^2) 时间。我认为没有办法对序列进行模式匹配,因此您最好将其转换为 LazyList 。在这里查看 brian 的精彩回答:stackoverflow.com/questions/1306140/… -
您可能会对此感兴趣。 stackoverflow.com/questions/4629734/…
-
客观上,这是一个未解决的问题。没有已知的方法来实现具有竞争力的高效纯 Eratosthenes 筛。您可以对其进行一些优化,但您永远不会接近命令式解决方案的性能,因此这是一项学术练习。如果你想编写快速的代码来解决这些问题,你必须接受杂质。此外,我相信纯和不纯之间的性能差距永远不会缩小,因为纯准确地抽象出您编写快速代码所需的信息。
-
@JonHarrop 确实working with lists有区别,类似于整数排序和比较排序的区别;但是纯代码的复杂性可以显着降低到接近最佳值(请参阅我在此线程中的回答)。但是working with immutable arrays 没有什么能阻止智能实现使用破坏性更新,从而实现命令式实现的真正最佳复杂性(理论上)。
-
@WillNess:我同意一个足够聪明的编译器可以神奇地将纯源代码优化为理论上具有竞争效率的不纯实现,但我们今天离拥有这种复杂程度的编译器还差得很远,我做到了不相信它会发生。这只是重新审视了 Lisp 的“足够聪明的编译器”神话。在实践中,构建能够进行更多优化的编译器会降低结果代码性能的可预测性,以至于它实际上毫无用处(只需看看斯大林方案编译器)。
标签: performance f# lazy-evaluation primes sieve-of-eratosthenes