尽管有一个答案给出了使用优先级队列(PQ)的算法,如在SkewBinomialHeap中,它可能不是适合这项工作的 PQ。Eratosthenes (iEoS) 的增量筛选器需要的是一个 PQ,它具有出色的性能,可以获取最小值并重新插入值,但在添加新值时不需要最终性能,因为 iSoE 仅添加为新值将质数的总和值取到范围的平方根(这是每次减少发生一次的重新插入次数的一小部分)。SkewBinomialHeap PQ 并没有提供比使用平衡二叉搜索树的内置 Map 更多的东西- 所有 O(log n) 操作 - 除了它稍微改变操作的权重以有利于 SoE 的要求。但是,SkewBinaryHeap 每次归约仍需要许多 O(log n) 操作。
PQ 实现为堆,更具体地作为二进制堆,甚至更具体地作为 MinHeap,几乎满足 iSoE 的要求,具有 O(1) 性能以获取最小值和 O(log n) 性能以用于重新插入和添加新条目,尽管性能实际上是 O(log n) 的一小部分,因为大多数重新插入发生在队列顶部附近,并且大多数新值的添加(无关紧要,因为它们不常见)发生在附近这些操作最有效的队列末尾。此外,MinHeap PQ 可以在一次 O(log n) 次(通常是一小部分)中有效地实现删除最小值和插入函数。然后,而不是 Map(实现为AVL 树) 其中有一个 O(log n) 操作,通常具有完整的“log n”范围,因为我们需要的最小值位于树的最左端最后一个叶子处,我们通常在根处添加和删除最小值,并且平均一次插入几个级别。因此,每次剔除减少时,MinHeap PQ 只能用于 O(log n) 操作的一小部分,而不是多个较大部分的 O(log n) 操作。
MinHeap PQ 可以用纯函数代码实现(没有实现“removeMin”,因为 iSoE 不需要它,但有一个“调整”函数用于分段),如下所示:
[<RequireQualifiedAccess>]
module MinHeap =
type MinHeapTreeEntry<'T> = class val k:uint32 val v:'T new(k,v) = { k=k;v=v } end
[<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>]
[<NoEquality; NoComparison>]
type MinHeapTree<'T> =
| HeapEmpty
| HeapOne of MinHeapTreeEntry<'T>
| HeapNode of MinHeapTreeEntry<'T> * MinHeapTree<'T> * MinHeapTree<'T> * uint32
let empty = HeapEmpty
let getMin pq = match pq with | HeapOne(kv) | HeapNode(kv,_,_,_) -> Some kv | _ -> None
let insert k v pq =
let kv = MinHeapTreeEntry(k,v)
let rec insert' kv msk pq =
match pq with
| HeapEmpty -> HeapOne kv
| HeapOne kv2 -> if k < kv2.k then HeapNode(kv,pq,HeapEmpty,2u)
else let nn = HeapOne kv in HeapNode(kv2,nn,HeapEmpty,2u)
| HeapNode(kv2,l,r,cnt) ->
let nc = cnt + 1u
let nmsk = if msk <> 0u then msk <<< 1
else let s = int32 (System.Math.Log (float nc) / System.Math.Log(2.0))
(nc <<< (32 - s)) ||| 1u //never ever zero again with the or'ed 1
if k <= kv2.k then if (nmsk &&& 0x80000000u) = 0u then HeapNode(kv,insert' kv2 nmsk l,r,nc)
else HeapNode(kv,l,insert' kv2 nmsk r,nc)
else if (nmsk &&& 0x80000000u) = 0u then HeapNode(kv2,insert' kv nmsk l,r,nc)
else HeapNode(kv2,l,insert' kv nmsk r,nc)
insert' kv 0u pq
let private reheapify kv k pq =
let rec reheapify' pq =
match pq with
| HeapEmpty -> HeapEmpty //should never be taken
| HeapOne kvn -> HeapOne kv
| HeapNode(kvn,l,r,cnt) ->
match r with
| HeapOne kvr when k > kvr.k ->
match l with //never HeapEmpty
| HeapOne kvl when k > kvl.k -> //both qualify, choose least
if kvl.k > kvr.k then HeapNode(kvr,l,HeapOne kv,cnt)
else HeapNode(kvl,HeapOne kv,r,cnt)
| HeapNode(kvl,_,_,_) when k > kvl.k -> //both qualify, choose least
if kvl.k > kvr.k then HeapNode(kvr,l,HeapOne kv,cnt)
else HeapNode(kvl,reheapify' l,r,cnt)
| _ -> HeapNode(kvr,l,HeapOne kv,cnt) //only right qualifies
| HeapNode(kvr,_,_,_) when k > kvr.k -> //need adjusting for left leaf or else left leaf
match l with //never HeapEmpty or HeapOne
| HeapNode(kvl,_,_,_) when k > kvl.k -> //both qualify, choose least
if kvl.k > kvr.k then HeapNode(kvr,l,reheapify' r,cnt)
else HeapNode(kvl,reheapify' l,r,cnt)
| _ -> HeapNode(kvr,l,reheapify' r,cnt) //only right qualifies
| _ -> match l with //r could be HeapEmpty but l never HeapEmpty
| HeapOne(kvl) when k > kvl.k -> HeapNode(kvl,HeapOne kv,r,cnt)
| HeapNode(kvl,_,_,_) when k > kvl.k -> HeapNode(kvl,reheapify' l,r,cnt)
| _ -> HeapNode(kv,l,r,cnt) //just replace the contents of pq node with sub leaves the same
reheapify' pq
let reinsertMinAs k v pq =
let kv = MinHeapTreeEntry(k,v)
reheapify kv k pq
let adjust f (pq:MinHeapTree<_>) = //adjust all the contents using the function, then rebuild by reheapify
let rec adjust' pq =
match pq with
| HeapEmpty -> pq
| HeapOne kv -> HeapOne(MinHeapTreeEntry(f kv.k kv.v))
| HeapNode (kv,l,r,cnt) -> let nkv = MinHeapTreeEntry(f kv.k kv.v)
reheapify nkv nkv.k (HeapNode(kv,adjust' l,adjust' r,cnt))
adjust' pq
使用上述模块,iSoE 可以通过车轮分解优化和使用高效的共感应流 (CIS) 编写如下:
type CIS<'T> = class val v:'T val cont:unit->CIS<'T> new(v,cont) = { v=v;cont=cont } end
type cullstate = struct val p:uint32 val wi:int new(cnd,cndwi) = { p=cnd;wi=cndwi } end
let primesPQWSE() =
let WHLPRMS = [| 2u;3u;5u;7u |] in let FSTPRM = 11u in let WHLCRC = int (WHLPRMS |> Seq.fold (*) 1u) >>> 1
let WHLLMT = int (WHLPRMS |> Seq.fold (fun o n->o*(n-1u)) 1u) - 1
let WHLPTRN =
let wp = Array.zeroCreate (WHLLMT+1)
let gaps (a:int[]) = let rec gap i acc = if a.[i]=0 then gap (i+1) (acc+1uy) else acc
{0..WHLCRC-1} |> Seq.fold (fun s i->
let ns = if a.[i]<>0 then wp.[s]<-2uy*gap (i+1) 1uy;(s+1) else s in ns) 0 |> ignore
Array.init (WHLCRC+1) (fun i->if WHLPRMS |> Seq.forall (fun p->(FSTPRM+uint32(i<<<1))%p<>0u)
then 1 else 0) |> gaps;wp
let inline whladv i = if i < WHLLMT then i + 1 else 0 in let advcnd c i = c + uint32 WHLPTRN.[i]
let inline culladv c p i = let n = c + uint32 WHLPTRN.[i] * p in if n < c then 0xFFFFFFFFu else n
let rec mkprm (n,wi,pq,(bps:CIS<_>),q) =
let nxt = advcnd n wi in let nxti = whladv wi
if nxt < n then (0u,0,(0xFFFFFFFFu,0,MinHeap.empty,bps,q))
elif n>=q then let bp,bpi = bps.v in let nc,nci = culladv n bp bpi,whladv bpi
let nsd = bps.cont() in let np,_ = nsd.v in let sqr = if np>65535u then 0xFFFFFFFFu else np*np
mkprm (nxt,nxti,(MinHeap.insert nc (cullstate(bp,nci)) pq),nsd,sqr)
else match MinHeap.getMin pq with | None -> (n,wi,(nxt,nxti,pq,bps,q))
| Some kv -> let ca,cs = culladv kv.k kv.v.p kv.v.wi,cullstate(kv.v.p,whladv kv.v.wi)
if n>kv.k then mkprm (n,wi,(MinHeap.reinsertMinAs ca cs pq),bps,q)
elif n=kv.k then mkprm (nxt,nxti,(MinHeap.reinsertMinAs ca cs pq),bps,q)
else (n,wi,(nxt,nxti,pq,bps,q))
let rec pCID p pi pq bps q = CIS((p,pi),fun()->let (np,npi,(nxt,nxti,npq,nbps,nq))=mkprm (advcnd p pi,whladv pi,pq,bps,q)
pCID np npi npq nbps nq)
let rec baseprimes() = CIS((FSTPRM,0),fun()->let np=FSTPRM+uint32 WHLPTRN.[0]
pCID np (whladv 0) MinHeap.empty (baseprimes()) (FSTPRM*FSTPRM))
let genseq sd = Seq.unfold (fun (p,pi,pcc) ->if p=0u then None else Some(p,mkprm pcc)) sd
seq { yield! WHLPRMS; yield! mkprm (FSTPRM,0,MinHeap.empty,baseprimes(),(FSTPRM*FSTPRM)) |> genseq }
上面的代码在大约 0.077 秒内计算前 100,000 个素数,在 0.977 秒内计算前 1,000,000 个素数,在大约 14.33 秒内计算前 10,000,000 个素数,在大约 221.87 秒内计算前 100,000,000 个素数,所有这些都在 i7-2700K (3.5GHz) 上作为 64 位代码。这个纯函数式代码比Dustin Cambell 的基于可变字典的代码稍快,增加了轮分解的常见优化,延迟添加基本素数,并使用更有效的 CID 全部添加(tryfsharp和ideone),但仍然是纯函数式的他不使用 Dictionary 类的代码. 然而,对于大约 20 亿(约 1 亿个素数)的较大素数范围,使用基于哈希表的 Dictionary 的代码将更快,因为 Dictionary 操作没有 O(log n) 因子,并且这种增益克服了计算使用字典哈希表的复杂性。
上述程序具有进一步的特征,即分解轮是参数化的,例如,可以通过将 WHLPRMS 设置为 [| 来使用非常大的轮。2u;3u;5u;7u;11u;13u;17u;19u |] 和 FSTPRM 到 23u 以获得大约三分之二的运行时间,对于一千万个素数大约为 9.34 秒,但请注意,这需要几秒钟在程序开始运行之前计算 WHLPTRN,无论素数范围如何,这都是一个恒定的开销。
比较分析:与纯函数式增量树折叠实现相比,该算法稍微快一些,因为 MinHeap 树的平均使用高度比折叠树的深度小两倍,但被一个等价物抵消遍历 PQ 树级别的能力中的常数因子损失,因为它基于二叉堆,需要处理每个堆级别的右叶和左叶以及任一方式的分支,而不是树的每个级别的单个比较折叠通常用较浅的树枝折叠。与其他基于 PQ 和 Map 的功能算法相比,改进通常是通过减少遍历各个树结构的每个级别的 O(log n) 操作数量的常数因素。
在 400 多年前Michael Eytzinger发明的基于谱系树的模型之后,MinHeap 通常实现为可变数组 二进制堆。我知道这个问题说对非功能性可变代码没有兴趣,但是如果必须避免使用所有使用可变性的子代码,那么出于性能原因,我们不能使用“在幕后”使用可变性的列表或 LazyList。因此,想象一下 MinHeap PQ 的以下备用可变版本由库提供,并在更大的主要性能范围内享受另一个超过 2 的因子:
[<RequireQualifiedAccess>]
module MinHeap =
type MinHeapTreeEntry<'T> = class val k:uint32 val v:'T new(k,v) = { k=k;v=v } end
type MinHeapTree<'T> = ResizeArray<MinHeapTreeEntry<'T>>
let empty<'T> = MinHeapTree<MinHeapTreeEntry<'T>>()
let getMin (pq:MinHeapTree<_>) = if pq.Count > 0 then Some pq.[0] else None
let insert k v (pq:MinHeapTree<_>) =
if pq.Count = 0 then pq.Add(MinHeapTreeEntry(0xFFFFFFFFu,v)) //add an extra entry so there's always a right max node
let mutable nxtlvl = pq.Count in let mutable lvl = nxtlvl <<< 1 //1 past index of value added times 2
pq.Add(pq.[nxtlvl - 1]) //copy bottom entry then do bubble up while less than next level up
while ((lvl <- lvl >>> 1); nxtlvl <- nxtlvl >>> 1; nxtlvl <> 0) do
let t = pq.[nxtlvl - 1] in if t.k > k then pq.[lvl - 1] <- t else lvl <- lvl <<< 1; nxtlvl <- 0 //causes loop break
pq.[lvl - 1] <- MinHeapTreeEntry(k,v); pq
let reinsertMinAs k v (pq:MinHeapTree<_>) = //do minify down for value to insert
let mutable nxtlvl = 1 in let mutable lvl = nxtlvl in let cnt = pq.Count
while (nxtlvl <- nxtlvl <<< 1; nxtlvl < cnt) do
let lk = pq.[nxtlvl - 1].k in let rk = pq.[nxtlvl].k in let oldlvl = lvl
let k = if k > lk then lvl <- nxtlvl; lk else k in if k > rk then nxtlvl <- nxtlvl + 1; lvl <- nxtlvl
if lvl <> oldlvl then pq.[oldlvl - 1] <- pq.[lvl - 1] else nxtlvl <- cnt //causes loop break
pq.[lvl - 1] <- MinHeapTreeEntry(k,v); pq
let adjust f (pq:MinHeapTree<_>) = //adjust all the contents using the function, then re-heapify
if pq <> null then
let cnt = pq.Count
if cnt > 1 then
for i = 0 to cnt - 2 do //change contents using function
let e = pq.[i] in let k,v = e.k,e.v in pq.[i] <- MinHeapTreeEntry (f k v)
for i = cnt/2 downto 1 do //rebuild by reheapify
let kv = pq.[i - 1] in let k = kv.k
let mutable nxtlvl = i in let mutable lvl = nxtlvl
while (nxtlvl <- nxtlvl <<< 1; nxtlvl < cnt) do
let lk = pq.[nxtlvl - 1].k in let rk = pq.[nxtlvl].k in let oldlvl = lvl
let k = if k > lk then lvl <- nxtlvl; lk else k in if k > rk then nxtlvl <- nxtlvl + 1; lvl <- nxtlvl
if lvl <> oldlvl then pq.[oldlvl - 1] <- pq.[lvl - 1] else nxtlvl <- cnt //causes loop break
pq.[lvl - 1] <- kv
pq
极客注意:我实际上期望可变版本提供更好的改进性能比,但由于嵌套的 if-then-else 代码结构和主要剔除值的随机行为,它在重新插入时陷入困境,这意味着大部分分支的 CPU 分支预测失败,导致每次剔除减少许多额外的 10 个 CPU 时钟周期以重建指令预取缓存。
该算法唯一的其他恒定因素性能增益是分段和使用多任务处理以获得与 CPU 内核数量成正比的性能增益;然而,就目前而言,这是迄今为止最快的纯函数式 SoE 算法,甚至使用函数式 MinHeap 的纯函数形式也击败了简单的命令式实现,例如Jon Harrop 的代码或Johan Kullbom 的阿特金筛子(他的时间安排有误因为他只计算了素数到 1000 万而不是第 1000 万个素数),但如果使用更好的优化,这些算法将快五倍。当我们添加更大轮子分解的多线程时,函数式代码和命令式代码之间大约 5 的比例将有所降低,因为命令式代码的计算复杂度比函数式代码增加得更快,并且多线程比函数式代码更能帮助较慢的函数式代码更快的命令式代码,因为后者越来越接近枚举找到的素数所需的时间的基本限制。
EDIT_ADD: 即使可以选择继续使用 MinHeap 的纯功能版本,增加效率填充剔除复合数组的最简单方法是将其归零,使其具有正确的大小,然后运行一个初始化函数,该函数可以写入每个可变数组索引不超过一次。虽然这不是严格的“功能性”,但它很接近,因为数组被初始化,然后再也不会被修改。
添加分段、多线程、可编程轮因子圆周和许多性能调整的代码如下(除了一些添加的新常量外,用于实现分段和多线程的额外调整代码大约是代码的下半部分从“prmspg”函数开始):
type prmsCIS = class val pg:uint16 val bg:uint16 val pi:int val cont:unit->prmsCIS
new(pg,bg,pi,nxtprmf) = { pg=pg;bg=bg;pi=pi;cont=nxtprmf } end
type cullstate = struct val p:uint32 val wi:int new(cnd,cndwi) = { p=cnd;wi=cndwi } end
let primesPQOWSE() =
let WHLPRMS = [| 2u;3u;5u;7u;11u;13u;17u |] in let FSTPRM = 19u in let WHLCRC = int(WHLPRMS |> Seq.fold (*) 1u)
let MXSTP = uint64(FSTPRM-1u) in let BFSZ = 1<<<11 in let NUMPRCS = System.Environment.ProcessorCount
let WHLLMT = int (WHLPRMS |> Seq.fold (fun o n->o*(n-1u)) 1u) - 1 in let WHLPTRN = Array.zeroCreate (WHLLMT+1)
let WHLRNDUP = let gaps (a:int[]) = let rec gap i acc = if a.[i]=0 then gap (i+1) (acc+1)
else acc in let b = a |> Array.scan (+) 0
Array.init (WHLCRC>>>1) (fun i->
if a.[i]=0 then 0 else let g=2*gap (i+1) 1 in WHLPTRN.[b.[i]]<-byte g;1)
Array.init WHLCRC (fun i->if WHLPRMS |> Seq.forall (fun p->(FSTPRM+uint32(i<<<1))%p<>0u) then 1 else 0)
|> gaps |> Array.scan (+) 0
let WHLPOS = WHLPTRN |> Array.map (uint32) |> Array.scan (+) 0u in let advcnd cnd cndi = cnd + uint32 WHLPTRN.[cndi]
let MINRNGSTP = if WHLLMT<=31 then uint32(32/(WHLLMT+1)*WHLCRC) else if WHLLMT=47 then uint32 WHLCRC<<<1 else uint32 WHLCRC
let MINBFRNG = uint32((BFSZ<<<3)/(WHLLMT+1)*WHLCRC)/MINRNGSTP*MINRNGSTP
let MINBFRNG = if MINBFRNG=0u then MINRNGSTP else MINBFRNG
let inline whladv i = if i < WHLLMT then i+1 else 0 in let inline culladv c p i = c+uint32 WHLPTRN.[i]*p
let rec mkprm (n,wi,pq,(bps:prmsCIS),q,lstp,bgap) =
let nxt,nxti = advcnd n wi,whladv wi
if n>=q then let p = (uint32 bps.bg<<<16)+uint32 bps.pg
let nbps,nxtcmpst,npi = bps.cont(),culladv n p bps.pi,whladv bps.pi
let pg = uint32 nbps.pg in let np = p+pg in let sqr = q+pg*((p<<<1)+pg) //only works to p < about 13 million
let nbps = prmsCIS(uint16 np,uint16(np>>>16),nbps.pi,nbps.cont) //therefore, algorithm only works to p^2 or about
mkprm (nxt,nxti,(MinHeap.insert nxtcmpst (cullstate(p,npi)) pq),nbps,sqr,lstp,(bgap+1us)) //1.7 * 10^14
else match MinHeap.getMin pq with
| None -> (uint16(n-uint32 lstp),bgap,wi,(nxt,nxti,pq,bps,q,n,1us)) //fix with q is uint64
| Some kv -> let ca,cs = culladv kv.k kv.v.p kv.v.wi,cullstate(kv.v.p,whladv kv.v.wi)
if n>kv.k then mkprm (n,wi,(MinHeap.reinsertMinAs ca cs pq),bps,q,lstp,bgap)
elif n=kv.k then mkprm (nxt,nxti,(MinHeap.reinsertMinAs ca cs pq),bps,q,lstp,(bgap+1us))
else (uint16(n-uint32 lstp),bgap,wi,(nxt,nxti,pq,bps,q,n,1us))
let rec pCIS p pg bg pi pq bps q = prmsCIS(pg,bg,pi,fun()->
let (npg,nbg,npi,(nxt,nxti,npq,nbps,nq,nl,ng))=mkprm (p+uint32 WHLPTRN.[pi],whladv pi,pq,bps,q,p,0us)
pCIS (p+uint32 npg) npg nbg npi npq nbps nq)
let rec baseprimes() = prmsCIS(uint16 FSTPRM,0us,0,fun()->
let np,npi=advcnd FSTPRM 0,whladv 0
pCIS np (uint16 WHLPTRN.[0]) 1us npi MinHeap.empty (baseprimes()) (FSTPRM*FSTPRM))
let prmspg nxt adj pq bp q =
//compute next buffer size rounded up to next even wheel circle so at least one each base prime hits the page
let rng = max (((uint32(MXSTP+uint64(sqrt (float (MXSTP*(MXSTP+4UL*nxt))))+1UL)>>>1)+MINRNGSTP)/MINRNGSTP*MINRNGSTP) MINBFRNG
let nxtp() = async {
let rec addprms pqx (bpx:prmsCIS) qx =
if qx>=adj then pqx,bpx,qx //add primes to queue for new lower limit
else let p = (uint32 bpx.bg<<<16)+uint32 bpx.pg in let nbps = bpx.cont()
let pg = uint32 nbps.pg in let np = p+pg in let sqr = qx+pg*((p<<<1)+pg)
let nbps = prmsCIS(uint16 np,uint16(np>>>16),nbps.pi,nbps.cont)
addprms (MinHeap.insert qx (cullstate(p,bpx.pi)) pqx) nbps sqr
let adjcinpg low k (v:cullstate) = //adjust the cull states for the new page low value
let p = v.p in let WHLSPN = int64 WHLCRC*int64 p in let db = int64 p*int64 WHLPOS.[v.wi]
let db = if k<low then let nk = int64(low-k)+db in nk-((nk/WHLSPN)*WHLSPN)
else let nk = int64(k-low) in if db<nk then db+WHLSPN-nk else db-nk
let r = WHLRNDUP.[int((((db>>>1)%(WHLSPN>>>1))+int64 p-1L)/int64 p)] in let x = int64 WHLPOS.[r]*int64 p
let r = if r>WHLLMT then 0 else r in let x = if x<db then x+WHLSPN-db else x-db in uint32 x,cullstate(p,r)
let bfbtsz = int rng/WHLCRC*(WHLLMT+1) in let nbuf = Array.zeroCreate (bfbtsz>>>5)
let rec nxtp' wi cnt = let _,nbg,_,ncnt = mkprm cnt in let nwi = wi + int nbg
if nwi < bfbtsz then nbuf.[nwi>>>5] <- nbuf.[nwi>>>5] ||| (1u<<<(nwi&&&0x1F)); nxtp' nwi ncnt
else let _,_,pq,bp,q,_,_ = ncnt in nbuf,pq,bp,q //results incl buf and cont parms for next page
let npq,nbp,nq = addprms pq bp q
return nxtp' 0 (0u,0,MinHeap.adjust (adjcinpg adj) npq,nbp,nq-adj,0u,0us) }
rng,nxtp() |> Async.StartAsTask
let nxtpg nxt (cont:(_*System.Threading.Tasks.Task<_>)[]) = //(len,pq,bp,q) =
let adj = (cont |> Seq.fold (fun s (r,_) -> s+r) 0u)
let _,tsk = cont.[0] in let _,pq,bp,q = tsk.Result
let ncont = Array.init (NUMPRCS+1) (fun i -> if i<NUMPRCS then cont.[i+1]
else prmspg (nxt+uint64 adj) adj pq bp q)
let _,tsk = ncont.[0] in let nbuf,_,_,_ = tsk.Result in nbuf,ncont
//init cond buf[0], no queue, frst bp sqr offset
let initcond = 0u,System.Threading.Tasks.Task.Factory.StartNew (fun()->
(Array.empty,MinHeap.empty,baseprimes(),FSTPRM*FSTPRM-FSTPRM))
let nxtcond n = prmspg (uint64 n) (n-FSTPRM) MinHeap.empty (baseprimes()) (FSTPRM*FSTPRM-FSTPRM)
let initcont = Seq.unfold (fun (n,((r,_)as v))->Some(v,(n+r,nxtcond (n+r)))) (FSTPRM,initcond)
|> Seq.take (NUMPRCS+1) |> Seq.toArray
let rec nxtprm (c,ci,i,buf:uint32[],cont) =
let rec nxtprm' c ci i =
let nc = c + uint64 WHLPTRN.[ci] in let nci = whladv ci in let ni = i + 1 in let nw = ni>>>5
if nw >= buf.Length then let (npg,ncont)=nxtpg nc cont in nxtprm (c,ci,-1,npg,ncont)
elif (buf.[nw] &&& (1u <<< (ni &&& 0x1F))) = 0u then nxtprm' nc nci ni
else nc,nci,ni,buf,cont
nxtprm' c ci i
seq { yield! WHLPRMS |> Seq.map (uint64);
yield! Seq.unfold (fun ((c,_,_,_,_) as cont)->Some(c,nxtprm cont))
(nxtprm (uint64 FSTPRM-uint64 WHLPTRN.[WHLLMT],WHLLMT,-1,Array.empty,initcont)) }
请注意,MinHeap 模块,无论是基于函数的还是基于数组的,都添加了一个“调整”函数,以允许在每个新段页面的开头调整每个线程版本的 PQ 的剔除状态。另请注意,可以调整代码,以便大多数计算使用 32 位范围完成,最终序列输出为 uint64,计算时间成本很低,因此目前的理论范围超过 100 万亿(十提高到十四次方)如果愿意等待大约三到四个月来计算该范围。数字范围检查已被删除,因为任何人都不太可能使用此算法来计算该范围,更不用说超过它了。
使用纯函数 MinHeap 和 2,3,5,7 轮分解,上述程序分别在 0.062、0.629、10.53 和 195.62 秒内计算前十万、一百万、一千万和一亿个素数。使用基于数组的 MinHeap 可以分别加快 0.097、0.276、3.48 和 51.60 秒。通过将 WHLPRMS 更改为“[| 2u;3u;5u;7u;11u;13u;17u |]”并将 FSTPRM 更改为 19u 来使用 2、3、5、7、11、13、17 轮,速度会更快一些分别为 0.181、0.308、2.49 和 36.58 秒(对于具有恒定开销的恒定因子改进)。这个最快的调整在大约 88.37 秒内计算出 32 位数字范围内的 203,280,221 个素数。“BFSZ”常数可以通过在较小范围的较慢时间和较大范围的较快时间之间进行权衡来调整,建议在较大范围内尝试使用“1<<<14”的值。此常量仅设置最小缓冲区大小,程序会自动将缓冲区大小调整为高于该大小以适应更大的范围,以便缓冲区足够,以便页面范围所需的最大基本素数将始终“打击”每个页面至少一次; 这意味着不需要额外的“桶筛”的复杂性和开销。使用“primesPQOWSE() |> Seq.takeWhile ((>=)100000000000UL) 测试,最后一个完全优化的版本可以在大约 256.8 和 3617.4 秒(对于 1000 亿只需要一个多小时)内计算高达 10 和 1000 亿的素数|> Seq.fold (fun sp -> s + 1UL) 0UL" 用于输出。
我不认为使用增量 SoE 算法来制作功能性或几乎功能性的代码运行得比这快得多。从代码中可以看出,优化基本增量算法大大增加了代码复杂性,因此它可能比基于直接数组剔除的等效优化代码稍微复杂一点,该代码的运行速度大约比这并且没有额外的性能指数意味着这个功能性增量代码具有不断增加的额外百分比开销。
那么除了从有趣的理论和知识观点来看,这是否有用?可能不是。对于最多约一千万的较小范围的素数,最好的基本未完全优化的增量功能 SoE 可能已经足够,并且与最简单的命令式 SoE 相比,编写起来非常简单,或者 RAM 内存使用量更少。但是,它们比使用数组的命令式代码要慢得多,因此对于高于该范围的范围,它们“失去动力”。虽然这里已经证明可以通过优化来加速代码,但它仍然比更命令式的纯基于数组的版本慢 10 倍,但增加了复杂性,至少与具有等效优化的代码一样复杂, 甚至在 DotNet 上的 F# 下的代码也比使用直接编译为本机代码的 C++ 等语言慢四倍;如果真的想研究大范围的素数,可能会使用其他语言和技术中的一种primesieve可以在四个小时内计算出百万亿范围内的素数,而不是这段代码所需的大约三个月。 END_EDIT_ADD