Задача 10 из Project Euler заключается в том, чтобы найти сумму всех простых чисел, указанных ниже.
Я решил это просто путем суммирования простых чисел, генерируемых ситом Эратосфена. Затем я встретил гораздо более эффективное решение Lucy_Hedgehog (сублинейное!).
При n = 2⋅10 ^ 9:
-
Код Python (из приведенной выше цитаты) выполняется через 1.2 секунды в Python 2.7.3.
-
Код С++ (мой) работает примерно через 0,3 секунды (скомпилирован с g++ 4.8.4).
Я повторил тот же алгоритм в 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:
I попробовал заменить неизменяемый Data.Map
на mutable Data.HashTable.IO.BasicHashTable
из пакета hashtables
, но я, вероятно, делаю что-то плохое, так как для крошечного n = 30 он занимает слишком много времени, около 10 секунд. Что не так?
Обновление 18.06.17:
Интересно отметить проблемы производительности HashTable. Я взял Sherh код, используя измененный Data.HashTable.ST.Linear
, но отброшен Data.Judy
вместо. Он работает через 1,1 секунды, все еще относительно медленно.