Mam program Haskell, który generuje ~ 280M danych tekstowych logowania podczas biegu wewnątrz monady ST. Tutaj następuje praktycznie całkowite zużycie pamięci (przy wyłączonym protokołowaniu program przydziela łącznie 3 MB pamięci rzeczywistej).Efektywne rejestrowanie danych ciągów w Haskell's ST Monad
Problem polega na tym, że zabrakło mi pamięci. Podczas gdy program uruchamia zużycie pamięci przekracza 1,5 GB, a na końcu kończy się, gdy próbuje zapisać ciąg dziennika do pliku.
Funkcja dziennika pobiera ciąg i gromadzi dane dziennika do konstruktora ciąg przechowywane w STRef w środowisku:
import qualified Data.ByteString.Lazy.Builder as BB
...
myLogFunction s = do
...
lift $ modifySTRef myStringBuilderRef (<> BB.stringUtf8 s)
Próbowałem wprowadzenie surowości stosując wzory huk i modifySTRef”, ale to sprawiło, zużycie pamięci nawet gorzej.
piszę ciąg dziennika zgodnie z zaleceniami dokumentacji hPutBuilder, tak:
hSetBinaryMode h True
hSetBuffering h $ BlockBuffering Nothing
BB.hPutBuilder h trace
ten zużywa kilka dodatkowych GBS pamięci. Próbowałem różnych ustawień buforowania i najpierw konwertowałem na leniwy ByteString (nieco lepiej).
Qs:
Jak mogę zminimalizować zużycie pamięci, gdy program działa? Spodziewałbym się, że biorąc pod uwagę ścisłą reprezentację ByteString i odpowiednią ilość ścisłości, potrzebowałbym trochę więcej pamięci niż ~ 280M rzeczywistych danych dziennika, które przechowuję.
Jak mogę zapisać wynik w pliku bez przydzielania pamięci? Nie rozumiem, dlaczego Haskell potrzebuje GB pamięci, aby przesłać tylko niektóre dane rezydentne do pliku.
Edit:
Oto profil pamięć o małym przebiegu (~ 42MB danych dziennika). Całkowite wykorzystanie pamięci to 3 MB z wyłączonym protokołowaniem.
15,632,058,700 bytes allocated in the heap
4,168,127,708 bytes copied during GC
343,530,916 bytes maximum residency (42 sample(s))
7,149,352 bytes maximum slop
931 MB total memory in use (0 MB lost due to fragmentation)
Tot time (elapsed) Avg pause Max pause
Gen 0 29975 colls, 0 par 5.96s 6.15s 0.0002s 0.0104s
Gen 1 42 colls, 0 par 6.01s 7.16s 0.1705s 1.5604s
TASKS: 3 (1 bound, 2 peak workers (2 total), using -N1)
SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
INIT time 0.00s ( 0.00s elapsed)
MUT time 32.38s (33.87s elapsed)
GC time 11.97s (13.31s elapsed)
RP time 0.00s ( 0.00s elapsed)
PROF time 0.00s ( 0.00s elapsed)
EXIT time 0.00s ( 0.00s elapsed)
Total time 44.35s (47.18s elapsed)
Alloc rate 482,749,347 bytes per MUT second
Productivity 73.0% of total user, 68.6% of total elapsed
Edit:
Pobiegłem profil pamięci o małym przebiegu dziennika jako zapytał:
profile http://imageshack.us/a/img14/9778/6a5o.png
Próbowałem dodawanie wzorców Bang !, deepseq $/$ !!, życie i takie w odpowiednich miejscach, ale nie wydaje się, żeby to miało jakikolwiek wpływ. W jaki sposób zmusić Haskella do wzięcia wyrażenia string/printf itd. I umieszczenia go w ciasnym ByteString zamiast utrzymywania wszystkich tych list [Char] i niedoszacowanych?
Edit:
Oto pełna rzeczywista funkcja śledzenia
trace s = do
enable <- asks envTraceEnable
when (enable) $ do
envtrace <- asks envTrace
let b = B8.pack s
lift $ b `seq` modifySTRef' envtrace (<> BB.byteString b)
Czy to 'surowe' wystarczy? Czy muszę uważać na wszystko, jeśli zadzwonię do tej funkcji typeclass w mojej monadie ReaderT/ST? Tylko dlatego, że jest tak naprawdę wywoływana i nie odkładana w żaden sposób.
do
trace $ printf "%i" myint
jest w porządku?
Dzięki!
Logowanie nie dotyczy stany, a więc sugerowałbym, żebyś użył monitu Writer'a do tego – Ankur
Nawet jeśli skonwertujesz z używania monady czytelnika z STRef do pisarza, mam taką samą sytuację. W końcu to monoid typu Builder. Wolałbym nie dodawać pisarza w stosie transformatora bez żadnego powodu. – NBFGRTW
Potrzebujemy więcej danych. Czy możesz pokazać nam profil sterty? W jaki sposób generowany jest twój dziennik? Jeśli użyjesz na przykład 'stringUtf8', to moje podejrzenie jest takie, że wynikowy' Builder' zawiera dużą liczbę odwołań do 'String', i tam właśnie idzie pamięć. –