Я разместил тот же вопрос в CodeReview, но не получил ответа. поэтому я попытаю счастья здесь, в SO.
Вот одна из моих программ, которая использовала мемоизацию и массив для повышения производительности и использования памяти. Производительность кажется удовлетворительной, но использование памяти смехотворно, и я не могу понять, что не так:
{-# LANGUAGE BangPatterns #-}
import Data.Functor
import Data.Array (Array)
import qualified Data.Array as Arr
import Control.DeepSeq
genColtzArr n = collatzArr
where collatzArr = Arr.array (1, n) $ take n $ map (\v -> (v, collatz v 0)) [1..]
collatz 1 !acc = 1 + acc
collatz !m !acc
| even m = go (m `div` 2) acc
| otherwise = go (3 * m + 1) acc
where go !l !acc
| l <= n = let !v = collatzArr Arr.! l in 1 + acc + v
| otherwise = collatz l $ 1 + acc
collatz
здесь означает этого парня. Эта функция должна получить число n
, а затем вернуть массив с индексацией от 1 до n
, и в котором каждая ячейка содержит длину ссылки от индекса до 1 по формуле Коллатца.
Но использование памяти этим методом настолько велико. Вот результат профилировщика (опция ghc -prof -fprof-auto -rtsopts
, опция времени выполнения +RTS -p
, n == 500000
):
total alloc = 730,636,136 bytes (excludes profiling overheads)
COST CENTRE MODULE %time %alloc
genColtzArr.collatz Main 40.4 34.7
genColtzArr.collatz.go Main 25.5 14.4
COST CENTRE MODULE no. entries %time %alloc %time %alloc
genColtzArr Main 105 1 0.0 0.0 74.7 72.1
genColtzArr.collatzArr Main 106 1 8.0 20.8 74.7 72.1
genColtzArr.collatzArr.\ Main 107 500000 0.9 2.2 66.8 51.3
genColtzArr.collatz Main 109 1182582 40.4 34.7 65.9 49.1
genColtzArr.collatz.go Main 110 1182581 25.5 14.4 25.5 14.4
Обратите внимание, что -O2
не является желаемым ответом. Я хочу выяснить, в чем проблема в этой программе и вообще, как мне определить неэффективность времени и памяти в коде Haskell. В частности, я понятия не имею, почему этот код с хвостовой рекурсией и шаблоном взрыва может потреблять так много памяти.
тот же код с -s
дает это:
1,347,869,264 bytes allocated in the heap
595,901,528 bytes copied during GC
172,105,056 bytes maximum residency (7 sample(s))
897,704 bytes maximum slop
315 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 2408 colls, 0 par 0.412s 0.427s 0.0002s 0.0075s
Gen 1 7 colls, 0 par 0.440s 0.531s 0.0759s 0.1835s
INIT time 0.000s ( 0.000s elapsed)
MUT time 0.828s ( 0.816s elapsed)
GC time 0.852s ( 0.958s elapsed)
RP time 0.000s ( 0.000s elapsed)
PROF time 0.000s ( 0.000s elapsed)
EXIT time 0.004s ( 0.017s elapsed)
Total time 1.684s ( 1.791s elapsed)
%GC time 50.6% (53.5% elapsed)
Alloc rate 1,627,861,429 bytes per MUT second
Productivity 49.4% of total user, 46.4% of total elapsed
так что занимает 300 мегабайт. это все еще слишком велико.
полный код
{-# LANGUAGE BangPatterns #-}
import Data.Functor
import Data.Array (Array)
import qualified Data.Array as Arr
import Control.DeepSeq
genColtzArr n = collatzArr
where collatzArr = Arr.array (1, n) $ take n $ map (\v -> (v, collatz v 0)) [1..]
collatz 1 !acc = 1 + acc
collatz !m !acc
| even m = go (m `div` 2) acc
| otherwise = go (3 * m + 1) acc
where go !l !acc
| l <= n = let !v = collatzArr Arr.! l in 1 + acc + v
| otherwise = collatz l $ 1 + acc
genLongestArr n = Arr.array (1, n) llist
where colatz = genColtzArr n
llist = (1, 1):zipWith (\(n1, a1) l2 ->
let l1 = colatz Arr.! a1
in (n1 + 1, if l2 < l1 then a1 else n1 + 1))
llist (tail $ Arr.elems colatz)
main :: IO ()
main = getLine >> do
ns <- map read <$> lines <$> getContents
let m = maximum ns
let lar = genLongestArr m
let iter [] = return ()
iter (h:t) = (putStrLn $ show $ lar Arr.! h) >> iter t
iter ns
-O0
работает быстро, это, по сути, случайность. Следующее, что вы можете сделать, это посмотреть на оптимизированное ядро. - person user2407038   schedule 09.03.2016try -O2
, нет, я хочу сосредоточиться на улучшении самой программы, а не полагаться на некоторую оптимизацию. - person Jason Hu   schedule 09.03.2016-O0
Haskell — компилятор делает все, что хочет, поэтому невероятно непредсказуемо, какие проходы ядра сработают. Единственный выход — посмотреть на результат-v3
и окончательное сгенерированное ядро. Если код выглядит до смешного плохо... может быть потому, что вам действительно нужно использовать-O2
с GHC. Никакого заговора нет,-O2
не является жульничеством - ваша программа может иметь никаких проблем с ней, и компилятор генерирует плохую программу, потому что-O0
означает делать это быстро, а не делать это хорошо. - person user2407038   schedule 09.03.2016collatzArr
иcollatz
, большой длинный ленивый массив, который нельзя собрать на лету, и т. д., но что у вас заmain
? Какое действие IO было оценено? - person zakyggaps   schedule 09.03.2016main
, невозможно воспроизвести ваше поведение таким образом, чтобы помочь другим. Например, без изменения вашего кода я получаю 70% производительности по сравнению с менее чем 50%, если я используюmain = print $ maximum $ genColtzArr 500000
с общим использованием памяти 128 МБ. - person Zeta   schedule 09.03.2016