Sto cercando di parallelizzare un ray-tracer. Ciò significa che ho un elenco molto lungo di piccoli calcoli. Il programma vanilla viene eseguito su una scena specifica in 67,98 secondi e 13 MB di utilizzo totale della memoria e una produttività del 99,2%.
Nel mio primo tentativo ho usato la strategia parallela parBuffer
con una dimensione del buffer di 50. Ho scelto parBuffer
perché scorre l'elenco solo alla velocità con cui si consumano le scintille e non forza la colonna vertebrale dell'elenco come parList
, il che userebbe molta memoria poiché l'elenco è molto lungo. Con -N2
, ha funzionato in un tempo di 100,46 secondi e 14 MB di utilizzo totale della memoria e una produttività del 97,8%. Le informazioni sulla scintilla sono:SPARKS: 480000 (476469 converted, 0 overflowed, 0 dud, 161 GC'd, 3370 fizzled)
L'elevata percentuale di scintille sbiadite indica che la granularità delle scintille era troppo piccola, quindi ho provato a utilizzare la strategia parListChunk
, che divide l'elenco in blocchi e crea una scintilla per ogni blocco. Ho ottenuto i migliori risultati con una dimensione di blocco di 0.25 * imageWidth
. Il programma è stato eseguito in 93,43 secondi e 236 MB di memoria totale e una produttività del 97,3%. Le informazioni scintilla è: SPARKS: 2400 (2400 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)
. Credo che l'uso della memoria molto maggiore sia dovuto parListChunk
al fatto che forza la spina dorsale della lista.
Quindi ho provato a scrivere la mia strategia che divideva pigramente l'elenco in blocchi e poi li ha passati a parBuffer
e concatenato i risultati.
concat $ withStrategy (parBuffer 40 rdeepseq) (chunksOf 100 (map colorPixel pixels))
Questo è stato eseguito in 95,99 secondi e 22 MB di utilizzo totale della memoria e il 98,8% di produttività. Questo ha avuto successo nel senso che tutte le scintille vengono convertite e l'utilizzo della memoria è molto inferiore, tuttavia la velocità non è migliorata. Ecco un'immagine di una parte del profilo del registro eventi.
Come puoi vedere, i thread vengono interrotti a causa di overflow dell'heap. Ho provato ad aggiungere +RTS -M1G
che aumenta la dimensione dell'heap predefinito fino a 1 GB. I risultati non sono cambiati. Ho letto che il thread principale di Haskell utilizzerà la memoria dall'heap se il suo stack va in overflow, quindi ho anche provato ad aumentare anche la dimensione dello stack predefinito, +RTS -M1G -K1G
ma anche questo non ha avuto alcun impatto.
C'è qualcos'altro che posso provare? Posso pubblicare informazioni di profilazione più dettagliate per l'utilizzo della memoria o il registro eventi se necessario, non ho incluso tutto perché sono molte informazioni e non pensavo che fosse necessario includerle tutte.
EDIT: stavo leggendo del supporto multicore Haskell RTS e parla dell'esistenza di un HEC (Haskell Execution Context) per ogni core. Ogni HEC contiene, tra le altre cose, un'area di allocazione (che fa parte di un singolo heap condiviso). Ogni volta che un'area di allocazione di HEC è esaurita, deve essere eseguita una raccolta dei rifiuti. Sembra essere un'opzione RTS per controllarlo, -A. Ho provato -A32M ma non ho visto differenze.
EDIT2: ecco un collegamento a un repository GitHub dedicato a questa domanda . Ho incluso i risultati di profilazione nella cartella di profilatura.
EDIT3: Ecco il bit di codice rilevante:
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 float casuali precalcolati e utilizzati da colorPixel. Il tipo di colorPixel
è:
colorPixel :: World -> ((Float,Float),([(Float,Float)],[(Float,Float)])) -> Color
Strategy
. Avrei dovuto scegliere una parola migliore. Inoltre, il problema di overflow dell'heap si verifica anche con parListChunk
e parBuffer
.
concat $ withStrategy …
? Non riesco a riprodurre questo comportamento in6008010
, che è il commit più vicino alla tua modifica.