2015-07-26 20 views
114

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. Event log profile

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 
+2

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

+3

Ho fatto un repo dedicato in modo che non mi ci confonda accidentalmente. Ho anche incluso tutte le informazioni di profilazione. –

+0

@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'. –

risposta

2
Non

la soluzione al vostro problema, ma un suggerimento per la causa:

Haskell sembra essere molto conservatore riutilizzo della memoria e quando l'interprete vede la possibilità di recuperare un blocco di memoria, va per questo. La descrizione del problema si adatta al comportamento GC minore descritto qui (in basso) https://wiki.haskell.org/GHC/Memory_Management.

I nuovi dati vengono allocati in "vivaio" da 512 kb. Una volta esaurito, "minore GC" si verifica - esegue la scansione della stanza dei bambini e libera i valori inutilizzati.

Quindi, se si chop i dati in blocchi più piccoli, si attiva il motore a fare la pulizia in precedenza - calci GC in