まず第一に、それは素晴らしいです。しかし、ベンチマークが奇妙な結果をもたらす状況に遭遇しました。私はHaskellを初めて使用しますが、可変配列とモナドで手を汚すのはこれが初めてです。以下のコードは、この例に基づいています。
for
範囲ではなく数値とステップ関数を受け取る一般的なモナディック関数を作成しました(のようforM_
に)。ジェネリックfor
関数(ループA)を使用することと、同等の再帰関数(ループB)を埋め込むことを比較しました。ループAを使用すると、ループBを使用するよりも著しく高速になります。奇妙なことに、ループAとBの両方を一緒に使用すると、ループBを単独で使用するよりも高速になります(ただし、ループAを単独で使用するよりもわずかに遅くなります)。
矛盾について私が考えることができるいくつかの可能な説明。これらは単なる推測であることに注意してください。
- Haskellがモナディック関数から結果を抽出する方法について私がまだ学んでいないこと。
- ループBは、ループAよりもキャッシュ効率の低い方法でアレイに障害を発生させます。なぜですか。
- 私はばかげた間違いをしました。ループAとループBは実際には異なります。
- ループAとループBのいずれかまたは両方がある3つのケースすべてで、プログラムは同じ出力を生成することに注意してください。
これがコードです。ghc -O2 for.hs
GHCバージョン6.10.4を使用してテストしました。
import Control.Monad
import Control.Monad.ST
import Data.Array.IArray
import Data.Array.MArray
import Data.Array.ST
import Data.Array.Unboxed
for :: (Num a, Ord a, Monad m) => a -> a -> (a -> a) -> (a -> m b) -> m ()
for start end step f = loop start where
loop i
| i <= end = do
f i
loop (step i)
| otherwise = return ()
primesToNA :: Int -> UArray Int Bool
primesToNA n = runSTUArray $ do
a <- newArray (2,n) True :: ST s (STUArray s Int Bool)
let sr = floor . (sqrt::Double->Double) . fromIntegral $ n+1
-- Loop A
for 4 n (+ 2) $ \j -> writeArray a j False
-- Loop B
let f i
| i <= n = do
writeArray a i False
f (i+2)
| otherwise = return ()
in f 4
forM_ [3,5..sr] $ \i -> do
si <- readArray a i
when si $
forM_ [i*i,i*i+i+i..n] $ \j -> writeArray a j False
return a
primesTo :: Int -> [Int]
primesTo n = [i | (i,p) <- assocs . primesToNA $ n, p]
main = print $ primesTo 30000000