When Generating Primes in F#, why is the “Sieve of Erosthenes” so slow in this particular implementatIon?
IE浏览器
我在这里做错了什么? 是否必须使用列表,序列和数组以及限制的工作方式?
因此,这里是设置:我正在尝试生成一些素数。 我看到有十亿个素数的十亿个文本文件。 问题不是为什么...问题是这个帖子上的人如何使用python计算毫秒内所有低于1,000,000的素数...而下面的F#代码在做什么呢?
1 2 3 4 5 6 7 8 9 10 11 12 | let sieve_primes2 top_number = let numbers = [ for i in 2 .. top_number do yield i ] let sieve (n:int list) = match n with | [x] -> x,[] | hd :: tl -> hd, List.choose(fun x -> if x%hd = 0 then None else Some(x)) tl | _ -> failwith"Pernicious list error." let rec sieve_prime (p:int list) (n:int list) = match (sieve n) with | i,[] -> i::p | i,n' -> sieve_prime (i::p) n' sieve_prime [1;0] numbers |
在FSI中启用计时器后,我得到100,000个CPU的价值为4.33秒……在那之后,这一切都爆了。
您的筛选功能很慢,因为您尝试筛选出
您可以避免生成偶数,但2不能从一开始就是素数。此外,
结合以上建议:
1 2 3 4 5 6 7 8 9 | let sieve_primes top_number = let numbers = [ yield 2 for i in 3..2..top_number -> i ] let rec sieve ns = match ns with | [] -> [] | x::xs when x*x > top_number -> ns | x::xs -> x::sieve (List.filter(fun y -> y%x <> 0) xs) sieve numbers |
在我的机器上,更新的版本非常快,对于
根据我的代码在这里:stackoverflow.com/a/8371684/124259
在22毫秒内获得fsi的前100万个素数-很重要的一点可能是此时正在编译代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #time"on" let limit = 1000000 //returns an array of all the primes up to limit let table = let table = Array.create limit true //use bools in the table to save on memory let tlimit = int (sqrt (float limit)) //max test no for table, ints should be fine let mutable curfactor = 1; while curfactor < tlimit-2 do curfactor <- curfactor+2 if table.[curfactor] then //simple optimisation let mutable v = curfactor*2 while v < limit do table.[v] <- false v <- v + curfactor let out = Array.create (100000) 0 //this needs to be greater than pi(limit) let mutable idx = 1 out.[0]<-2 let mutable curx=1 while curx < limit-2 do curx <- curx + 2 if table.[curx] then out.[idx]<-curx idx <- idx+1 out |
对于使用列表的常规试验划分算法(@pad),以及使用Eratosthenes筛网(SoE)选择筛分数据结构的数组(@John Palmer和@Jon Harrop),都有几个不错的答案。但是,@ pad的列表算法并不是特别快,并且会在更大的筛分范围内"爆炸",并且@John Palmer的阵列解决方案更加复杂,使用的内存多于必要,并使用外部可变状态,因此与程序使用命令式语言(例如C#)编写。
EDIT_ADD:我编辑了下面的代码(带有行注释的旧代码),修改了序列表达式,以避免某些函数调用,以反映更多的"迭代器样式",尽管它节省了20%的速度,但仍然没有与真正的C#迭代器的速度接近,后者的速度与"滚动自己的枚举器"最终F#代码的速度大致相同。我已经相应地修改了以下计时信息。 END_EDIT
下面的真正的SoE程序仅使用64 KB的内存来筛选高达一百万的素数(由于仅考虑了奇数并使用打包的位BitArray),并且仍然与@John Palmer的程序一样快(约40毫秒)。在i7 2700K(3.5 GHz)上只有一百万行,只有几行代码:
1 2 3 4 5 6 7 8 9 10 11 | open System.Collections let primesSoE top_number= let BFLMT = int((top_number-3u)/2u) in let buf = BitArray(BFLMT+1,true) let SQRTLMT = (int(sqrt (double top_number))-3)/2 let rec cullp i p = if i <= BFLMT then (buf.[i] <- false; cullp (i+p) p) for i = 0 to SQRTLMT do if buf.[i] then let p = i+i+3 in cullp (p*(i+1)+i) p seq { for i = -1 to BFLMT do if i<0 then yield 2u elif buf.[i] then yield uint32(3+i+i) } // seq { yield 2u; yield! seq { 0..BFLMT } |> Seq.filter (fun i->buf.[i]) // |> Seq.map (fun i->uint32 (i+i+3)) } primesSOE 1000000u |> Seq.length;; |
由于序列运行时库的效率低下以及每个函数调用大约需要28个时钟周期进行枚举本身并花费大约16个函数调用返回的代价,因此几乎所有经过的时间都花在最后两行中,以枚举找到的质数每次迭代。通过滚动我们自己的迭代器,可以减少每次迭代的几个函数调用,但是代码并不那么简洁。请注意,在下面的代码中,除了筛分数组的内容和使用对象表达式实现迭代器所必需的引用变量之外,没有暴露任何可变状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | open System open System.Collections open System.Collections.Generic let primesSoE top_number= let BFLMT = int((top_number-3u)/2u) in let buf = BitArray(BFLMT+1,true) let SQRTLMT = (int(sqrt (double top_number))-3)/2 let rec cullp i p = if i <= BFLMT then (buf.[i] <- false; cullp (i+p) p) for i = 0 to SQRTLMT do if buf.[i] then let p = i+i+3 in cullp (p*(i+1)+i) p let nmrtr() = let i = ref -2 let rec nxti() = i:=!i+1;if !i<=BFLMT && not buf.[!i] then nxti() else !i<=BFLMT let inline curr() = if !i<0 then (if !i= -1 then 2u else failwith"Enumeration not started!!!") else let v = uint32 !i in v+v+3u { new IEnumerator<_> with member this.Current = curr() interface IEnumerator with member this.Current = box (curr()) member this.MoveNext() = if !i< -1 then i:=!i+1;true else nxti() member this.Reset() = failwith"IEnumerator.Reset() not implemented!!!" interface IDisposable with member this.Dispose() = () } { new IEnumerable<_> with member this.GetEnumerator() = nmrtr() interface IEnumerable with member this.GetEnumerator() = nmrtr() :> IEnumerator } primesSOE 1000000u |> Seq.length;; |
上面的代码在大约8.5毫秒的时间内将同一机器上的质数筛选到100万,这是因为每次迭代的函数调用数量从大约16大大减少到了大约3。这与以相同样式编写的C#代码的速度大致相同。我在第一个示例中使用的F#迭代器样式无法像C#迭代器那样自动生成IEnumerable样板代码,这很糟糕,但是我想这是序列的意图-只是它们是如此低效以至于无法提高性能由于被实现为序列计算表达式。
现在,在列举主要结果上花费了不到一半的时间,以更好地利用CPU时间。
What am I doing wrong here?
您已经实现了一种不同的算法,该算法遍历每个可能的值,并使用
您不能有效地浏览列表,因为它们不支持随机访问,因此请使用数组。