2013-08-28 21 views
8

Jestem nowy w Haskell i programowania funkcjonalnego i mam program, który działa, ale przepełnia stos po kilku sekundach. Moje pytanie brzmi: co mam teraz zrobić? Jak mogę uzyskać chociaż wskazówkę, gdzie to się dzieje, wydrukować stos lub cokolwiek?Debugowanie przepełnienia stosu w haskell

Program działa bardzo wolno podczas działania w ghci z: trace, więc przepełnienie stosu nie występuje. Nie występuje również w Runhaskell, który po prostu spożywa coraz więcej pamięci. Błąd pojawia się tylko podczas kompilacji za pomocą ghc i wykonywania.

+1

jak skompilowałeś? ghc -O2 blah.hs może być w stanie zoptymalizować lepiej –

+0

Dzięki, ale to nie pomogło – Philippe

+0

czy mógłbyś podać link do pastebin do kodu? – jev

Odpowiedz

1

Zobacz http://book.realworldhaskell.org/read/profiling-and-optimization.html dla ogólnych wytycznych dotyczących profilowania

+2

Próbowałem podanych tam poleceń profilowania, ale nie otrzymuję żadnych wyników, ponieważ mój program ulega awarii i nie kończy się w sposób czysty. – Philippe

+0

Mój zły, po prostu zawiedli opcje. Profilowanie działa nawet w przypadku awarii programu, ale nie jestem pewien, w jaki sposób może mi pomóc, nie chcę zoptymalizować całego programu, wystarczy rozwiązać problem. – Philippe

1

Najprostszą strategią jest za pomocą funkcji śledzenia. Np rozważyć tę funkcję:

badFunction :: Int -> Int 
badFunction x 
| x < 10 = x * 2 
| x == 15 = badFunction 480 
| even x = badFunction $ x `div` 2 
| odd x = badFunction $ x + 1 

main = print . badFunction . read . head =<< getArgs 

Np jeśli uruchomić ./program 13, dostaniesz 42. Jeśli jednak uruchomisz ./program 29, otrzymasz przepełnienie stosu.

W tym celu debugowania, umieść trace oświadczenia dla każdego przypadku (od Debug.Trace):

badFunction :: Int -> Int 
badFunction x 
| x < 10 = trace ("badF -> small " ++ show x) x * 6 
| x == 15 = trace "badF -> x == 15" $ badFunction 480 
| even x = trace ("badF -> even " ++ show x) $ badFunction $ x `div` 2 
| odd x = trace ("badF -> odd " ++ show x) badFunction $ x + 1 

trace ma typ String -> a -> a i drukuje dany fragment, a następnie zwraca wartość drugiego argumentu. Jest to funkcja specjalna, ponieważ wykonuje IO w czystej funkcji. Jest świetny do debugowania.

W tym przypadku, uruchamiając program z ./program 19 teraz, dostaniesz wyjście:

badF -> odd 19 
badF -> even 20 
badF -> even 10 
badF -> small 5 
30 

Pokazuje dokładnie to, co nazwano.

Jeśli teraz uruchomić go z ./program 29 dostaniesz:

badF -> odd 29 
badF -> even 30 
badF -> x == 15 
badF -> even 960 
badF -> even 480 
badF -> even 240 
badF -> even 120 
badF -> even 60 
badF -> even 30 
badF -> x == 15 
badF -> even 960 
badF -> even 480 
badF -> even 240 
badF -> even 120 
badF -> even 60 
badF -> even 30 
badF -> x == 15 
badF -> even 960 
badF -> even 480 
badF -> even 240 
badF -> even 120 
badF -> even 60 
badF -> even 30 
badF -> x == 15 
badF -> even 960 
.... 

To dość wyraźnie pokazuje, jak pętla występuje. Podczas gdy w tym przykładzie było oczywiste, gdzie jest problem, jest on przydatny w przypadku bardziej złożonych funkcji (szczególnie, jeśli przepełnienie stosu obejmuje wiele funkcji - po prostu wykonaj to za pomocą wszystkich funkcji, które podejrzewasz, że mogą być problemem).

+0

Mój program działa poprawnie, funkcje są wywoływane, gdy powinny. Nie ma nieskończonej pętli poza główną pętlą (i powinna być nieskończona). Podejrzewam, że problem pochodził z leniwej oceny i nie sądzę, żeby pokazanie, która funkcja jest nazywana, pomogłoby. – Philippe

3

W Twoim przypadku jest to problem z ograniczeniem, który powoduje przepełnienie stosu. Jednym z łatwych sposobów na znalezienie takich problemów jest użycie deepseq library. To dodaje kilka funkcji, które pozwalają w pełni ocenić wartość (która jest lepsza niż seq, która idzie tylko o jeden poziom w dół). Kluczową funkcją jest force :: NFData a => a -> a. To bierze wartość, w pełni ją ocenia i zwraca.

Działa to tylko dla typów, które implementują klasę typu NFData. Na szczęście istnieje makro szablonu haskell w deepseq-th library: deriveNFData. Jest używany z własnymi typami danych, np. deriveNFData ''BfMachine.

Aby użyć, należy umieścić force $ przed swoimi funkcjami, które mogą mieć problemy ze ścisłością (lub liftM force $ dla funkcji monadycznych).Na przykład z kodem, kładę go przed step, ponieważ był funkcyjny w pliku:

{-# LANGUAGE TemplateHaskell #-} 
import Data.Char 
import Debug.Trace 
import Control.DeepSeq 
import Control.DeepSeq.TH 
import Control.Monad (liftM) 

type Stack = [Int] 

data BfMachine = BfMachine 
    { program :: String 
    , pc :: Int 
    , stack :: Stack 
    , sp :: Int 
    } deriving Show 
deriveNFData ''BfMachine 

setElem :: [Int] -> Int -> Int -> [Int] 
setElem list n value = map (\(i, v) -> if i == n then value else v) (zip [0..] list) 

step :: BfMachine -> IO (BfMachine) 
step [email protected](BfMachine { program = program, pc = pc, stack = stack, sp = sp }) = liftM force $ 
    case program !! pc of 
    '-' -> return m { pc = pc + 1, stack = setElem stack sp ((stack !! sp) - 1) } 
    '+' -> return m { pc = pc + 1, stack = setElem stack sp ((stack !! sp) + 1) } 
    '<' -> return m { pc = pc + 1, sp = sp - 1 } 
    '>' -> return m { pc = pc + 1, sp = sp + 1 } 
    '[' -> return $ if stack !! sp /= 0 then m { pc = pc + 1 } 
        else m { pc = (findNextBracket program $ pc + 1) + 1 } 
    ']' -> return m { pc = findPrevBracket program $ pc - 1 } 
    '.' -> do putChar $ chr $ stack !! sp 
       return m { pc = pc + 1 } 
    ',' -> do c <- getChar 
       let s' = setElem stack sp $ ord c 
       in return m { stack = s', pc = pc + 1 } 
    a -> return m { pc = pc + 1 } 

findNextBracket :: String -> Int -> Int 
findNextBracket program pos = 
    case program !! pos of 
    '[' -> findNextBracket program $ (findNextBracket program $ pos + 1) + 1 
    ']' -> pos 
    x -> findNextBracket program (pos + 1) 

findPrevBracket :: String -> Int -> Int 
findPrevBracket program pos = 
    case program !! pos of 
    ']' -> findPrevBracket program $ (findPrevBracket program $ pos - 1) - 1 
    '[' -> pos 
    x -> findPrevBracket program (pos - 1) 

isFinished :: BfMachine -> Bool 
isFinished [email protected](BfMachine { program = p, pc = pc }) 
    | pc == length p = True 
    | otherwise = False 

run :: BfMachine -> IO() 
run m = do 
    if isFinished m then 
     return() 
    else do 
     m <- step m 
     run m 

fib = ">++++++++++>+>+[ [+++++[>++++++++<-]>.<++++++[>--------<-]+<<<]>.>>[ [-]<[>+<-]>>[<<+>+>-]<[>+<-[>+<-[>+<-[>+<-[>+<-[>+<- [>+<-[>+<-[>+<-[>[-]>+>+<<<-[>+<-]]]]]]]]]]]+>>> ]<<< ] This program doesn't terminate; you will have to kill it. Daniel B Cristofani (cristofdathevanetdotcom) http://www.hevanet.com/cristofd/brainfuck/" 
main = run BfMachine { program = fib , pc = 0, stack = replicate 1024 0, sp = 0 } 

To rzeczywiście rozwiązuje problem - nawet po kilku minutach jazdy, nie rozbił się i pamięć wykorzystanie wynosi tylko 3,2 MB.

Możesz trzymać się tego rozwiązania lub próbować znaleźć prawdziwy problem związany ze ścisłością (co czyni wszystko ścisłym). Robi się to przez usunięcie siły z funkcji step i wypróbowanie jej na funkcjach pomocniczych, których używa (np. setElem, findPrevBacket itd.). Okazuje się, że winowajcą jest setElem, dzięki czemu przed tą funkcją rozwiązuje się również problem ze ścisłością. Zgaduję, że to dlatego, że if w lambda mapy oznacza, że ​​większość wartości nigdy nie musi być oceniana na liście, i prawdopodobnie budować ogromne tony podczas trwania programu.

+0

Hmm ... Miałem nadzieję na coś lepszego, ale to deepseq nie jest takie złe, dziękuję i dziękuję za debugowanie. Jeśli chcę zmusić setElem do oceny wszystkiego na liście, czy możliwe jest użycie "!" notacja i unikanie deepseq? – Philippe

Powiązane problemy