リスト(@pad)を使用した一般的な試行除算アルゴリズムと、エラトステネスのふるい(SoE)(@JohnPalmerおよび@JonHarrop)を使用したふるい分けデータ構造の配列の選択の両方に関して、いくつかの良い答えがあります。ただし、@ padのリストアルゴリズムは特に高速ではなく、ふるい分け範囲が広い場合は「爆発」します。@ John Palmerの配列ソリューションはやや複雑で、必要以上にメモリを使用し、外部の可変状態を使用するため、プログラムは、C#などの命令型言語で作成されました。
EDIT_ADD: 以下のコード(行コメント付きの古いコード)を編集して、「イテレータースタイル」をより反映するように一部の関数呼び出しを回避するようにシーケンス式を変更しましたが、速度を20%節約しましたが、それでも反映されません。 「独自の列挙子をロールする」最終的なF#コードとほぼ同じ速度の真のC#イテレーターの速度に近づきます。それに応じて、以下のタイミング情報を変更しました。 END_EDIT
次の真のSoEプログラムは、64 Kバイトのメモリを使用して最大100万までの素数をふるいにかけます(奇数を考慮し、パックされたビットBitArrayを使用するため)。 i7 2700K(3.5 GHz)で100万、数行のコードのみ:
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の関数呼び出しで戻るため、検出された素数を列挙するために、経過時間のほとんどすべてが最後の2行で費やされます。反復ごと。これは、独自のイテレータをローリングすることで、反復ごとに数回の関数呼び出しに減らすことができますが、コードはそれほど簡潔ではありません。次のコードでは、シービング配列の内容と、オブジェクト式を使用したイテレーターの実装に必要な参照変数以外に、変更可能な状態が公開されていないことに注意してください。
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;;
上記のコードは、反復ごとの関数呼び出しの数を約16から約3に大幅に減らすため、同じマシンで素数を100万にふるいにかけるのに約8.5ミリ秒かかります。これは、同じスタイルで記述されたC#コードとほぼ同じ速度です。 。最初の例で使用したF#のイテレータースタイルが、C#イテレーターのようにIEnumerableボイラープレートコードを自動的に生成しないのは残念ですが、それがシーケンスの意図であると思います。シーケンス計算式として実装されているため。
現在、CPU時間をはるかに有効に活用するために、主要な結果を列挙するために費やされる時間は半分未満です。