Sto provando a parallelizzare un ray-tracer. Ciò significa che ho una lunga lista di piccoli calcoli. Il programma vanilla gira su una scena specifica in 67,98 secondi e 13 MB di memoria totale e il 99,2% di produttività.L'overflow dell'heap dei thread Haskell nonostante l'utilizzo della memoria totale di soli 22 MB?
Nel mio primo tentativo ho usato la strategia parallela parBuffer
con una dimensione del buffer di 50. Ho scelto parBuffer
perché cammina attraverso la lista unica veloce come scintille sono consumati, e non forza la spina dorsale della lista come parList
, che userebbe molta memoria dato che la lista è molto lunga. Con -N2
, ha funzionato in un tempo di 100,46 secondi e 14 MB di memoria totale e 97,8% di produttività. Le informazioni scintilla è: SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)
La gran parte delle scintille svanito indica che la granularità di scintille era troppo piccolo, così la prossima ho provato ad utilizzare la strategia parListChunk
, che divide la lista in pezzi e crea una scintilla per ogni blocco. Ho ottenuto i migliori risultati con una dimensione del blocco di 0.25 * imageWidth
. Il programma è stato eseguito in 93,43 secondi e 236 MB di memoria totale e 97,3% di produttività. Le informazioni sulla scintilla sono: SPARKS: 2400 (2400 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
. Credo che l'uso della memoria molto maggiore sia dovuto al fatto che lo parListChunk
impone il dorso della lista.
Quindi ho provato a scrivere la mia strategia che ha diviso pigramente l'elenco in blocchi e quindi ha passato i blocchi a parBuffer
e concatenato i risultati.
concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map colorPixel pixels))
Questo ha funzionato in 95,99 secondi e 22 MB di memoria totale e il 98,8% di produttività. Questo è stato un successo, nel senso che tutte le scintille sono state convertite e l'utilizzo della memoria è molto più basso, tuttavia la velocità non è migliorata. Ecco un'immagine di parte del profilo di eventlog.
Come si può vedere i thread vengono arrestati a causa di overflow di heap. Ho provato ad aggiungere +RTS -M1G
che aumenta le dimensioni di heap predefinite fino a 1 Gb. I risultati non sono cambiati. Ho letto che il thread principale di Haskell userà la memoria dall'heap se il suo stack è in overflow, quindi ho anche provato ad aumentare la dimensione dello stack di default con +RTS -M1G -K1G
ma questo non ha avuto alcun impatto.
C'è altro che posso provare? Posso pubblicare informazioni di profilazione più dettagliate per l'utilizzo della memoria o il registro degli eventi, se necessario, non ho incluso tutto perché sono molte informazioni e non pensavo che fosse necessario includerle.
EDIT: Stavo leggendo sul Haskell RTS multicore support e si parla di un HEC (Haskell Execution Context) per ogni core. Ogni HEC contiene, tra le altre cose, un'area di allocazione (che è una parte di un singolo heap condiviso). Ogni volta che una qualsiasi Area di allocazione dell'HEC è esaurita, deve essere eseguita una raccolta di dati inutili. Sembra essere un RTS option per controllarlo, -A. Ho provato -A32M ma non ho visto alcuna differenza.
EDIT2: Here is a link to a github repo dedicated to this question. Ho incluso i risultati del profilo nella cartella di profilazione.
Edit3: Ecco il bit rilevante di codice:
render :: [([(Float,Float)],[(Float,Float)])] -> World -> [Color]
render grids world = cs where
ps = [ (i,j) | j <- reverse [0..wImgHt world - 1] , i <- [0..wImgWd world - 1] ]
cs = map (colorPixel world) (zip ps grids)
--cs = withStrategy (parListChunk (round (wImgWd world)) rdeepseq) (map (colorPixel world) (zip ps grids))
--cs = withStrategy (parBuffer 16 rdeepseq) (map (colorPixel world) (zip ps grids))
--cs = concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map (colorPixel world) (zip ps grids)))
Le griglie sono carri casuali che sono precalcolate e utilizzato da colorPixel.Il tipo di colorPixel
è:
colorPixel :: World -> ((Float,Float),([(Float,Float)],[(Float,Float)])) -> Color
Potrebbe fornire l'esatto commit in cui si è tentato 'concat $ withStrategy ...'.? Non riesco a riprodurre questo comportamento in ['6008010'] (https://github.com/jrraymond/ray-tracer/tree/6008010), che è il commit più vicino alla tua modifica. – Zeta
Ho fatto un repo dedicato in modo che non mi ci confonda accidentalmente. Ho anche incluso tutte le informazioni di profilazione. –
@dfeuer quando ho detto definire la mia strategia non intendevo "strategia". Avrei dovuto scegliere una parola migliore. Inoltre, il problema dell'overflow dell'heap si verifica anche con 'parListChunk' e' parBuffer'. –