Debug eines Stack-Überlaufs in haskell

8

Ich bin neu in Haskell und funktionaler Programmierung und ich habe ein Programm, das funktioniert, aber den Stack nach einigen Sekunden überläuft. Meine Frage ist, was soll ich von hier aus machen? Wie kann ich zumindest einen Hinweis darauf bekommen, wo es auftritt, den Stapel ausdrucken oder irgendetwas?

Das Programm ist sehr langsam, wenn es in ghci mit: trace ausgeführt wird, so dass der Stapelüberlauf nicht auftritt. Es tritt auch nicht mit runhaskell auf, die nur mehr und mehr Gedächtnis essen werden. Ich bekomme den Fehler nur beim Übersetzen mit ghc und Ausführen.

    
Philippe 28.08.2013, 19:45
quelle

3 Antworten

3

In Ihrem Fall ist es ein Striktheitsproblem, das den Stapelüberlauf verursacht. Eine wirklich einfache Möglichkeit, solche Probleme zu finden, ist die Verwendung der deepseq-Bibliothek . Dies fügt ein paar Funktionen hinzu, mit denen Sie einen Wert vollständig auswerten können (was besser ist als seq , was nur eine Ebene nach unten geht). Die Schlüsselfunktion ist force :: NFData a => a -> a . Dies nimmt einen Wert, bewertet es vollständig und gibt es zurück.

Es funktioniert jedoch nur für Typen, die die Klasse NFData type implementieren. Glücklicherweise gibt es in der deepseq-ten Bibliothek ein Template-Haskell-Makro: deriveNFData . Dies wird mit Ihren eigenen Datentypen verwendet, zB deriveNFData ''BfMachine .

Um zu verwenden, setzen Sie force $ vor Ihre Funktionen, die Striktheitsprobleme haben können (oder liftM force $ für monadische Funktionen). ZB mit deinem Code, stelle ich es vor step , da das die Schlüsselfunktion in der Datei war:

%Vor%

Dies löst das Problem tatsächlich - selbst nach ein paar Minuten Laufzeit ist es nicht abgestürzt und die Speicherauslastung beträgt nur 3,2 MB.

Sie können bei dieser Lösung bleiben oder versuchen zu finden, wo das wahre Striktheitsproblem ist (wie das alles streng macht). Dies tun Sie, indem Sie die Erzwingung aus der Funktion step entfernen und sie auf die Hilfsfunktionen anwenden, die sie verwendet (zB setElem , findPrevBacket usw.). Es stellt sich heraus, dass setElem der Täter ist, indem force vor diese Funktion gestellt wird, löst auch das Striktheitsproblem. Ich nehme an, es ist, weil die if in der Karte Lambda bedeutet, dass die meisten Werte nie in der Liste ausgewertet werden müssen, und möglicherweise riesige Thunks aufbauen, während das Programm fortfährt.

    
David Miani 29.08.2013 09:38
quelle
1

Siehe Ссылка für allgemeine Richtlinien zum Profiling

    
nponeccop 28.08.2013 19:54
quelle
1

Die einfachste Strategie ist die Verwendung der Trace-Funktion. Betrachten Sie diese Funktion zB:

%Vor%

Wenn Sie beispielsweise ./program 13 ausführen, erhalten Sie 42 . Wenn Sie ./program 29 ausführen, erhalten Sie jedoch einen Stapelüberlauf.

Um dies zu debuggen, plazieren Sie trace -Anweisungen für jeden Fall (von Debug.Trace ):

%Vor%

trace hat den Typ String -> a -> a und gibt die angegebene Zeichenfolge aus und gibt dann den Wert des zweiten Arguments zurück. Es ist eine spezielle Funktion, da es IO in einer reinen Funktion ausführt. Es ist jedoch großartig für das Debuggen.

Wenn Sie in diesem Fall das Programm jetzt mit ./program 19 ausführen, erhalten Sie die Ausgabe:

%Vor%

Zeigt genau an, was aufgerufen wurde.

Wenn Sie es jetzt mit ./program 29 ausführen, erhalten Sie:

%Vor%

Diese hübsche Darstellung zeigt deutlich, wie die Schleife abläuft. Während in diesem Beispiel ziemlich offensichtlich war, wo das Problem lag, ist es für komplexere Funktionen nützlich (besonders wenn der Stapelüberlauf mehrere Funktionen umfasst - tun Sie dies nur mit allen Funktionen, die Sie vermuten, könnte das Problem sein).

    
David Miani 29.08.2013 06:54
quelle