質問
Erlang でプログラムする方法を学ぶために projecteuler.net の問題に取り組んでいますが、200 万以下のすべての素数を 1 分以内に作成できる素数ジェネレーターを作成するのに最も苦労しています。シーケンシャル スタイルを使用して、エラトステネスのふるいを含む 3 種類のジェネレーターをすでに作成しましたが、どれも十分なパフォーマンスを発揮しません。
同時実行 Sieve はうまく機能すると考えましたが、 bad_arity メッセージが表示され、その理由がわかりません。問題が発生する理由、またはそれを適切にコーディングする方法について何か提案はありますか?
これが私のコードです。コメントアウトされたセクションは、同時実行を試みた場所です。
-module(primeserver). -compile(export_all). start() -> register(primes, spawn(fun() -> loop() end)). is_prime(N) -> rpc({is_prime,N}). rpc(Request) -> primes ! {self(), Request}, receive {primes, Response} -> Response end. loop() -> receive {From, {is_prime, N}} -> if N From ! {primes, false}; N =:= 2 -> From ! {primes, true}; N rem 2 =:= 0 -> From ! {primes, false}; true -> Values = is_not_prime(N), Val = not(lists:member(true, Values)), From ! {primes, Val} end, loop() end. for(N,N,_,F) -> [F(N)]; for(I,N,S,F) when I + S [F(I)|for(I+S, N, S, F)]; for(I,N,S,F) when I + S =:= N -> [F(I)|for(I+S, N, S, F)]; for(I,N,S,F) when I + S > N -> [F(I)]. get_list(I, Limit) -> if I [I*A || A [] end. is_not_prime(N) -> for(3, N, 2, fun(I) -> List = get_list(I,trunc(N/I)), lists:member(N,lists:flatten(List)) end ). %%L = for(1,N, fun() -> spawn(fun(I) -> wait(I,N) end) end), %%SeedList = [A || A %% lists:foreach(fun(X) -> %% Pid ! {in_list, X} %% end, SeedList) %% end, L). %%wait(I,N) -> %% List = [I*A || A lists:member(X,List) %% end.
解決
「badarity」エラーは、間違った数の引数を使用して「fun」を呼び出そうとしていることを意味します。この場合...
%%L = for(1,N, fun() -> spawn(fun(I) -> wait(I,N) end) end),
for/3 関数はアリティ 1 の fun を期待し、spawn/1 関数はアリティ 0 の fun を期待します。代わりにこれを試してください:
L = for(1, N, fun(I) -> spawn(fun() -> wait(I, N) end) end),
spawn に渡される fun は、その環境 (つまり I) の必要な部分を継承するため、明示的に渡す必要はありません。
素数の計算はいつでも楽しいものですが、これは Erlang が解決するために設計された種類の問題ではないことに注意してください。Erlang は、大規模なアクター スタイルの同時実行向けに設計されています。データ並列計算のすべての例では、パフォーマンスがかなり悪くなる可能性が高くなります。多くの場合、 たとえば ML における逐次的なソリューション 非常に高速なので、Erlang が追いつくにはコアがいくつあっても不十分です。 F# と .NET タスク並列ライブラリ 確かに、この種の作戦にははるかに優れた手段となるでしょう。
他のヒント
Go とチャネルを使用してエラトステネスクの同時プライム シーブを作成しました。
コードは次のとおりです。 http://github.com/aht/gosieve
私はそれについてここでブログに書きました: http://blog.onideas.ws/eratosthenes.go
このプログラムは、最初の 100 万個の素数 (15,485,863 までのすべての素数) を約 10 秒でふるい分けることができます。sieve は同時実行ですが、アルゴリズムは主に同期的です。ゴルーチン (お好みで言えば「アクター」) 間に必要な同期ポイントが多すぎるため、自由に並行して移動することができません。
考慮すべきもう 1 つの代替方法は、確率的素数生成を使用することです。Joe の本 (「プライム サーバー」) には、Miller-Rabin を使用した例があると思います...
素数を見つけるための 4 つの異なる Erlang 実装が見つかります (そのうち 2 つはエラトステネスのふるいに基づいています) ここ. 。このリンクには、4 つのソリューションのパフォーマンスを比較するグラフも含まれています。
エラトステネスのふるいは実装が非常に簡単ですが、お気づきのように、最も効率的ではありません。アトキンのふるいを試したことがありますか?
素数並列アルゴリズム: http://www.cs.cmu.edu/~scandal/cacm/node8.html
2 つの迅速な単一プロセス erlang プライム ジェネレーター。sprimes は 2m 未満のすべての素数を約 2.7 秒で生成します。私のコンピューター (2.4 GHz Core 2 Duo を搭載した Macbook) では、fprimes は約 3 秒で生成します。どちらもエラトステネスのふるいに基づいていますが、Erlang は配列ではなくリストで最もよく機能するため、両方とも消去されていない素数のリストを保持し、現在の頭で割り切れるかどうかをチェックし、検証された素数のアキュムレータを保持します。どちらも、リストの初期削減を行うためのプライム ホイールも実装しています。
-module(primes).
-export([sprimes/1, wheel/3, fprimes/1, filter/2]).
sieve([H|T], M) when H=< M -> [H|sieve([X || X<- T, X rem H /= 0], M)];
sieve(L, _) -> L.
sprimes(N) -> [2,3,5,7|sieve(wheel(11, [2,4,2,4,6,2,6,4,2,4,6,6,2,6,4,2,6,4,6,8,4,2,4,2,4,8,6,4,6,2,4,6,2,6,6,4,2,4,6,2,6,4,2,4,2,10,2,10], N), math:sqrt(N))].
wheel([X|Xs], _Js, M) when X > M ->
lists:reverse(Xs);
wheel([X|Xs], [J|Js], M) ->
wheel([X+J,X|Xs], lazy:next(Js), M);
wheel(S, Js, M) ->
wheel([S], lazy:lazy(Js), M).
fprimes(N) ->
fprimes(wheel(11, [2,4,2,4,6,2,6,4,2,4,6,6,2,6,4,2,6,4,6,8,4,2,4,2,4,8,6,4,6,2,4,6,2,6,6,4,2,4,6,2,6,4,2,4,2,10,2,10], N), [7,5,3,2], N).
fprimes([H|T], A, Max) when H*H =< Max ->
fprimes(filter(H, T), [H|A], Max);
fprimes(L, A, _Max) -> lists:append(lists:reverse(A), L).
filter(N, L) ->
filter(N, N*N, L, []).
filter(N, N2, [X|Xs], A) when X < N2 ->
filter(N, N2, Xs, [X|A]);
filter(N, _N2, L, A) ->
filter(N, L, A).
filter(N, [X|Xs], A) when X rem N /= 0 ->
filter(N, Xs, [X|A]);
filter(N, [_X|Xs], A) ->
filter(N, Xs, A);
filter(_N, [], A) ->
lists:reverse(A).
Lazy:lazy/1 と Lazy:next/1 は、疑似遅延無限リストの単純な実装を指します。
lazy(L) ->
repeat(L).
repeat(L) -> L++[fun() -> L end].
next([F]) -> F()++[F];
next(L) -> L.
sieve による素数生成は、同時実行には適していません (ただし、これまでに作成したすべての並列フィルターの追加オーバーヘッドを正当化できるほど操作は複雑ではありませんが、割り算のチェックに並列処理を使用することはできます)。
`
プロジェクト オイラーの問題 (最初の 50 個以上ではないにしてもほとんど) は、境界を選択する際に少しの創意工夫を加えた強引な問題がほとんどです。
N が素数であるかどうかを (総当たりで) テストすることを忘れないでください。N/2 ではなく、floor(sqrt(N)) + 1 までの素数で割り切れるかどうかだけを確認する必要があります。
幸運を
プロジェクト・オイラーが大好きです。
素数ジェネレーターに関して言えば、私はエラトステネスの篩の大ファンです。
2,000,000 未満の数値を目的とする場合は、単純な isPrime チェックの実装を試すことができます。erlang でどのように実行するかはわかりませんが、ロジックは単純です。
For Each NUMBER in LIST_OF_PRIMES
If TEST_VALUE % NUMBER == 0
Then FALSE
END
TRUE
if isPrime == TRUE add TEST_VALUE to your LIST_OF_PRIMES
iterate starting at 14 or so with a preset list of your beginning primes.
C# は、1 分未満でこのようなリストを 2,000,000 件実行しました。
編集: 余談ですが、エラトステネスのふるいは簡単に実装でき、すぐに実行できますが、巨大なリストに入り始めると扱いにくくなります。ブール配列と int 値を使用する最も単純な実装は、非常に高速に実行されます。問題は、値のサイズと配列の長さの制限に遭遇し始めることです。-- 文字列またはビット配列の実装に切り替えると便利ですが、大きな値でリストを反復処理するという課題がまだあります。
ここにvbバージョンがあります
'Sieve of Eratosthenes
'http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
'1. Create a contiguous list of numbers from two to some highest number n.
'2. Strike out from the list all multiples of two (4, 6, 8 etc.).
'3. The list's next number that has not been struck out is a prime number.
'4. Strike out from the list all multiples of the number you identified in the previous step.
'5. Repeat steps 3 and 4 until you reach a number that is greater than the square root of n (the highest number in the list).
'6. All the remaining numbers in the list are prime.
Private Function Sieve_of_Eratosthenes(ByVal MaxNum As Integer) As List(Of Integer)
'tested to MaxNum = 10,000,000 - on 1.8Ghz Laptop it took 1.4 seconds
Dim thePrimes As New List(Of Integer)
Dim toNum As Integer = MaxNum, stpw As New Stopwatch
If toNum > 1 Then 'the first prime is 2
stpw.Start()
thePrimes.Capacity = toNum 'size the list
Dim idx As Integer
Dim stopAT As Integer = CInt(Math.Sqrt(toNum) + 1)
'1. Create a contiguous list of numbers from two to some highest number n.
'2. Strike out from the list all multiples of 2, 3, 5.
For idx = 0 To toNum
If idx > 5 Then
If idx Mod 2 <> 0 _
AndAlso idx Mod 3 <> 0 _
AndAlso idx Mod 5 <> 0 Then thePrimes.Add(idx) Else thePrimes.Add(-1)
Else
thePrimes.Add(idx)
End If
Next
'mark 0,1 and 4 as non-prime
thePrimes(0) = -1
thePrimes(1) = -1
thePrimes(4) = -1
Dim aPrime, startAT As Integer
idx = 7 'starting at 7 check for primes and multiples
Do
'3. The list's next number that has not been struck out is a prime number.
'4. Strike out from the list all multiples of the number you identified in the previous step.
'5. Repeat steps 3 and 4 until you reach a number that is greater than the square root of n (the highest number in the list).
If thePrimes(idx) <> -1 Then ' if equal to -1 the number is not a prime
'not equal to -1 the number is a prime
aPrime = thePrimes(idx)
'get rid of multiples
startAT = aPrime * aPrime
For mltpl As Integer = startAT To thePrimes.Count - 1 Step aPrime
If thePrimes(mltpl) <> -1 Then thePrimes(mltpl) = -1
Next
End If
idx += 2 'increment index
Loop While idx < stopAT
'6. All the remaining numbers in the list are prime.
thePrimes = thePrimes.FindAll(Function(i As Integer) i <> -1)
stpw.Stop()
Debug.WriteLine(stpw.ElapsedMilliseconds)
End If
Return thePrimes
End Function