プロジェクトオイラーの問題10は、与えられたnの下にあるすべての素数の合計を見つけることです。
エラトステネスのふるいによって生成された素数を合計するだけで解決しました。次に、Lucy_Hedgehog(劣線形!)によるはるかに効率的なソリューションに出会いました。
以下のためのn = ^ 92⋅10:
私はそれを学んでいるので、Haskellで同じアルゴリズムを再実装しました:
import Data.List
import Data.Map (Map, (!))
import qualified Data.Map as Map
problem10 :: Integer -> Integer
problem10 n = (sieve (Map.fromList [(i, i * (i + 1) `div` 2 - 1) | i <- vs]) 2 r vs) ! n
where vs = [n `div` i | i <- [1..r]] ++ reverse [1..n `div` r - 1]
r = floor (sqrt (fromIntegral n))
sieve :: Map Integer Integer -> Integer -> Integer -> [Integer] -> Map Integer Integer
sieve m p r vs | p > r = m
| otherwise = sieve (if m ! p > m ! (p - 1) then update m vs p else m) (p + 1) r vs
update :: Map Integer Integer -> [Integer] -> Integer -> Map Integer Integer
update m vs p = foldl' decrease m (map (\v -> (v, sumOfSieved m v p)) (takeWhile (>= p*p) vs))
decrease :: Map Integer Integer -> (Integer, Integer) -> Map Integer Integer
decrease m (k, v) = Map.insertWith (flip (-)) k v m
sumOfSieved :: Map Integer Integer -> Integer -> Integer -> Integer
sumOfSieved m v p = p * (m ! (v `div` p) - m ! (p - 1))
main = print $ problem10 $ 2*10^9
でコンパイルしてghc -O2 10.hs
実行しましたtime ./10
。
正解ですが、約7秒かかります。
でコンパイルしてghc -prof -fprof-auto -rtsopts 10
実行しました./10 +RTS -p -h
。
10.profは、decrease
52.2%の時間と67.5%の割り当てが必要であることを示しています。
実行した後、hp2ps 10.hp
私はそのようなヒーププロファイルを取得しました:
ここでもdecrease
、ヒープの大部分を占めるように見えます。GHCバージョン7.6.3。
このHaskellコードの実行時間をどのように最適化しますか?
アップデート13.06.17:
私が試した不変の交換Data.Map
可変とData.HashTable.IO.BasicHashTable
からhashtables
小さなためにあるため、パッケージが、私はおそらく何かの悪いやってるのn = 30、それはすでに10秒程度、時間がかかりすぎます。どうしましたか?
アップデート18.06.17:
HashTableのパフォーマンスの問題に興味があるのは良い読み物です。ミュータブルを使用してSherhのコードを取得しましData.HashTable.ST.Linear
たがData.Judy
、代わりに立ち寄りました。1.1秒で実行されますが、それでも比較的低速です。
私はいくつかの小さな改善を行ったので3.4-3.5
、私のマシンでは数秒で実行されます。使用IntMap.Strict
することは大いに役立ちました。それ以外は、ghc
念のために手動でいくつかの最適化を実行しました。そして、HaskellコードをリンクからのPythonコードにもっと近づけます。次のステップとして、いくつかの可変を使用してみることができますHashMap
。しかし、私にはわかりません...IntMap
不変のコンテナであるため、いくつかの可変コンテナよりもはるかに高速になることはできません。私はまだそれの効率に驚いていますが。これをもっと早く実装できるといいのですが。
コードは次のとおりです。
import Data.List (foldl')
import Data.IntMap.Strict (IntMap, (!))
import qualified Data.IntMap.Strict as IntMap
p :: Int -> Int
p n = (sieve (IntMap.fromList [(i, i * (i + 1) `div` 2 - 1) | i <- vs]) 2 r vs) ! n
where vs = [n `div` i | i <- [1..r]] ++ [n', n' - 1 .. 1]
r = floor (sqrt (fromIntegral n) :: Double)
n' = n `div` r - 1
sieve :: IntMap Int -> Int -> Int -> [Int] -> IntMap Int
sieve m' p' r vs = go m' p'
where
go m p | p > r = m
| m ! p > m ! (p - 1) = go (update m vs p) (p + 1)
| otherwise = go m (p + 1)
update :: IntMap Int -> [Int] -> Int -> IntMap Int
update s vs p = foldl' decrease s (takeWhile (>= p2) vs)
where
sp = s ! (p - 1)
p2 = p * p
sumOfSieved v = p * (s ! (v `div` p) - sp)
decrease m v = IntMap.adjust (subtract $ sumOfSieved v) v m
main :: IO ()
main = print $ p $ 2*10^(9 :: Int)
更新:
ミュータブルhashtables
を使用し~5.5sec
て、この実装でHaskellのパフォーマンスを実現することができました。
また、いくつかの場所でリストの代わりにボックス化されていないベクトルを使用しました。Linear
ハッシュが最速のようです。これはもっと速くできると思います。パッケージにsse42
オプションがあることに気づきましたhasthables
。正しく設定できたかどうかはわかりませんが、それがなくてもそれほど高速に実行されます。
更新2(19.06.2017)
3x
ジュディハッシュマップを完全に削除することで、@ Kromからの(私のコードと彼のマップを使用した)より高速で最良のソリューションを実現することができました。代わりに、単純な配列のみが使用されます。あなたがのためのキーことに気づく場合は、同じ考えを思い付くことができS
ハッシュマップからいずれかの配列されている1
のn'
かn div i
のためにi
から1
にr
。したがって、このようなHashMapを、検索キーに応じて配列内でルックアップを行う2つの配列として表すことができます。
私のコード+ Judy HashMap
$ time ./judy
95673602693282040
real 0m0.590s
user 0m0.588s
sys 0m0.000s
私のコード+私のまばらなマップ
$ time ./sparse
95673602693282040
real 0m0.203s
user 0m0.196s
sys 0m0.004s
これは、IOUArray
すでに生成されたベクトルとVector
ライブラリの代わりに使用され、readArray
に置き換えられた場合、さらに高速に実行できますunsafeRead
。しかし、これを可能な限り最適化することに本当に興味がないのであれば、これを行うべきではないと思います。
このソリューションとの比較は不正行為であり、公平ではありません。PythonとC ++で実装された同じアイデアがさらに高速になることを期待しています。しかし、クローズドハッシュマップを使用した@Kromソリューションは、標準のデータ構造ではなくカスタムデータ構造を使用しているため、すでに不正行為を行っています。少なくとも、Haskellの標準的で最も人気のあるハッシュマップはそれほど高速ではないことがわかります。このような問題には、より優れたアルゴリズムとより優れたアドホックデータ構造を使用する方がよい場合があります。
この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。
侵害の場合は、連絡してください[email protected]
コメントを追加