Ich habe einige Erfahrung mit dem Schreiben kleiner Werkzeuge in Haskell und finde es sehr intuitiv zu bedienen, insbesondere zum Schreiben von Filtern (unter Verwendung von Filtern interact
), die ihre Standardeingabe verarbeiten und an die Standardausgabe weiterleiten.
Kürzlich habe ich versucht, einen solchen Filter für eine Datei zu verwenden, die etwa 10-mal so groß war wie gewöhnlich, und ich habe eine Stack space overflow
Fehlermeldung erhalten.
Nach einigem Lesen (z. B. hier und hier ) habe ich zwei Richtlinien identifiziert, um Stapelspeicher zu sparen (erfahrene Haskeller, bitte korrigieren Sie mich, wenn ich etwas schreibe, das nicht korrekt ist):
- Vermeiden Sie rekursive Funktionsaufrufe, die nicht tail-rekursiv sind (dies gilt für alle funktionalen Sprachen, die die Tail-Call-Optimierung unterstützen).
- Einführung
seq
in die Erzwingung einer frühen Auswertung von Unterausdrücken, damit die Ausdrücke nicht zu groß werden, bevor sie reduziert werden (dies gilt speziell für Haskell oder zumindest für Sprachen, die eine verzögerte Auswertung verwenden).
Nach fünf oder sechs seq
Aufrufen in meinem Code läuft mein Tool wieder reibungslos (auch auf den größeren Daten). Allerdings finde ich den Originalcode etwas besser lesbar.
Da ich kein erfahrener Haskell-Programmierer bin, wollte ich fragen, ob das Einführen seq
auf diese Weise eine gängige Praxis ist und wie oft es normalerweise seq
im Haskell-Produktionscode vorkommt. Oder gibt es Techniken, die es ermöglichen, zu häufiges Verwenden seq
zu vermeiden und dennoch wenig Stapelspeicher zu verwenden?
Antworten:
Leider gibt es Fälle
seq
, in denen man ein effizientes / gut funktionierendes Programm für große Datenmengen verwenden muss. In vielen Fällen können Sie daher im Produktionscode nicht darauf verzichten. Weitere Informationen finden Sie in Real World Haskell, Kapitel 25. Profilerstellung und Optimierung .Es gibt jedoch Möglichkeiten, eine
seq
direkte Verwendung zu vermeiden . Dies kann den Code sauberer und robuster machen. Einige Ideen:interact
. Lazy IO hat bekanntermaßen Probleme mit der Verwaltung von Ressourcen (nicht nur Speicher), und Iteratees sind genau darauf ausgelegt, diese zu überwinden. (Ich würde empfehlen, Lazy IO zu vermeiden, egal wie groß Ihre Daten sind - siehe Das Problem mit Lazy I / O. )seq
direkt Kombinatoren wie foldl ' oder foldr' oder strenge Versionen von Bibliotheken (wie Data.Map.Strict oder Control.Monad.State.Strict ) zu verwenden (oder eigene zu entwerfen ), die für strenge Berechnungen ausgelegt sind.seq
durch strengen Mustervergleich. In einigen Fällen kann es auch nützlich sein, strenge Konstruktorfelder zu deklarieren .rseq
) oder Full NF (rdeepseq
). Es gibt viele nützliche Methoden zum Arbeiten mit Sammlungen, Kombinieren von Strategien usw.quelle
ByteString
.