Проблемы с 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)
...