Проблемы с Haskell Heap с параметром Passing Style
Вот простая программа, которая ударяет мою кучу в Kingdom Come:
intersect n k z s rs c
| c == 23 = rs
| x == y = intersect (n+1) (k+1) (z+1) (z+s) (f : rs) (c+1)
| x < y = intersect (n+1) k (z+1) s rs c
| otherwise = intersect n (k+1) z s rs c
where x = (2*n*n) + 4 * n
y = (k * k + k )
f = (z, (x `div` 2), (z+s))
p = intersect 1 1 1 0 [] 0
main = do
putStr (show p)
Что делает программа, так это вычисление пересечения двух бесконечных рядов, останавливаясь при достижении 23 элементов. Но это не важно для меня.
Интересно, что, насколько я могу судить, здесь не должно быть много места, сидящего на куче. Функция пересекается с рекурсией со всеми рекурсиями, записанными как хвостовые вызовы. Государство накапливается в аргументах, и его не так много. 5 целых чисел и небольшой список кортежей.
Если бы я был игроком на пари, я бы поспорил, что в аргументах, как я делаю рекурсию, создаются какие-то трюки, особенно по аргументам, которые не оцениваются по заданной рекурсии. Но это просто дикая догадка.
Какая истинная проблема здесь? И как это исправить?
Ответы
Ответ 1
Если у вас есть проблема с кучей, запустите кучный профилировщик, например:
$ ghc -O2 --make A.hs -prof -auto-all -rtsopts -fforce-recomp
[1 of 1] Compiling Main ( A.hs, A.o )
Linking A.exe ...
Что при запуске:
$ ./A.exe +RTS -M1G -hy
Создает выходной файл A.hp
:
$ hp2ps -c A.hp
Так же:
![enter image description here]()
Итак, ваша куча полна Integer
, что указывает на некоторую проблему в накопительных параметрах ваших функций - где все Integer
.
Изменение функции так, чтобы она была строкой в ленивых аргументах Integer
(на основе того факта, что вы никогда не проверяете их значение), например:
{-# LANGUAGE BangPatterns #-}
intersect n k !z !s rs c
| c == 23 = rs
| x == y = intersect (n+1) (k+1) (z+1) (z+s) (f : rs) (c+1)
| x < y = intersect (n+1) k (z+1) s rs c
| otherwise = intersect n (k+1) z s rs c
where x = (2*n*n) + 4 * n
y = (k * k + k )
f = (z, (x `div` 2), (z+s))
p = intersect 1 1 1 0 [] 0
main = do
putStr (show p)
И ваша программа теперь работает в постоянном пространстве со списком аргументов, которые вы производите (хотя не заканчивается на c == 23
в любое разумное время).
![enter image description here]()
Ответ 2
Если вам нужно вернуть результирующий список в обратном порядке, вы можете использовать ленивость Haskell и вернуть список, как он вычисляется, вместо того, чтобы передавать его рекурсивно как накопительный аргумент. Это не только позволяет вам потреблять и распечатывать список по мере его вычисления (тем самым устраняя одну утечку пространства прямо там), вы также можете отбросить решение о том, сколько элементов вы хотите от intersect
:
{-# LANGUAGE BangPatterns #-}
intersect n k !z s
| x == y = f : intersect (n+1) (k+1) (z+1) (z+s)
| x < y = intersect (n+1) k (z+1) s
| otherwise = intersect n (k+1) z s
where x = (2*n*n) + 4 * n
y = (k * k + k )
f = (z, (x `div` 2), (z+s))
p = intersect 1 1 1 0
main = do
putStrLn (unlines (map show (take 23 p)))
Как заметил Дон, нам нужно быть осторожным, чтобы накапливать аргументы, чтобы они оценивали своевременно, а не наращивали большие трюки. Сделав аргумент z
strict, мы гарантируем, что потребуются все аргументы.
Вывод одного элемента на строку, мы можем наблюдать результат:
$ ghc -O2 intersect.hs && ./intersect
[1 of 1] Compiling Main ( intersect.hs, intersect.o )
Linking intersect ...
(1,3,1)
(3,15,4)
(10,120,14)
(22,528,36)
(63,4095,99)
(133,17955,232)
(372,139128,604)
(780,609960,1384)
...