2012-10-28 10 views
9

Ho una funzione che restituisce un'azione IO,azioni Sequencing IO in parallelo

f :: Int -> IO Int 

Vorrei calcolare questa funzione in parallelo per più valori dell'argomento. La mia implementazione ingenuo è stato il seguente:

import Control.Parallel.Strategies 

vals = [1..10] 
main = do 
     results <- mapM f vals 
     let results' = results `using` parList rseq 
     mapM_ print results' 

Il mio ragionamento per questo è stato che il primo mapM lega qualcosa di tipo IO [Int] a results, results' applica una strategia parallela alla lista contenuta, e il mapM_, infine, chiede i valori effettivi da stampandoli - ma ciò che deve essere stampato è già stato generato in parallelo, quindi il programma dovrebbe essere parallelo.

Dopo essere stato contento che effettivamente utilizza tutte le mie CPU, ho notato che il programma è meno efficace (come in tempo di parete) quando viene eseguito con +RTS -N8 piuttosto che senza alcun flag RTS. L'unica spiegazione che posso pensare è che il primo mapM deve eseguire una sequenza - cioè eseguire - tutte le azioni IO già, ma ciò non comporterebbe l'inefficacia, ma rendere l'esecuzione N8 efficace come quella non parallela, perché tutto il lavoro è fatto dal filo conduttore. L'esecuzione del programma con +RTS -N8 -s produce SPARKS: 36 (11 converted, 0 overflowed, 0 dud, 21 GC'd, 4 fizzled), che sicuramente non è ottimale, ma sfortunatamente non riesco a capirci nulla.

Suppongo di aver trovato uno dei trampolini di lancio per principianti nella parallelizzazione di Haskell o all'interno della monade IO. Che cosa sto facendo di sbagliato?

Informazioni di base: f n è una funzione che restituisce la soluzione per il problema di Project Euler n. Poiché molti di loro hanno dati da leggere, inserisco il risultato nella monade IO. Un esempio di come può sembrare è

-- Problem 13: Work out the first ten digits of the sum of one-hundred 50-digit numbers. 

euler 13 = fmap (first10 . sum) numbers 
     where 
      numbers = fmap (map read . explode '\n') $ readFile "problem_13" 
      first10 n 
        | n < 10^10 = n -- 10^10 is the first number with 11 digits 
        | otherwise = first10 $ n `div` 10 

Il file completo può essere trovato here (E 'un po' lungo, ma le prime funzioni "Euler X" dovrebbe essere sufficiente rappresentante), il file principale dove faccio il parallelismo è this one.

+0

È difficile diagnosticare senza vedere altro. Se lo esegui con '+ RTS -s -N', quali sono le statistiche delle scintille convertite/potate/fizzled? E 'f n 'restituisce un thunk che può effettivamente essere acceso? –

+0

@DanielFischer Ero un po 'titubante a pubblicare il file completo perché è piuttosto lungo (esempi minimi e simili). Ho pensato che il mio errore fosse nel codice parallelo, quindi mi sono concentrato su questo nella mia domanda. Ora ho aggiunto un nuovo paragrafo e anche le statistiche '-s' (che sono terribili). – David

+0

Non sono sicuro che quelli che stanno effettivamente facendo I/O lo distruggerebbero, ma per quelli puri (non usando 'Data.Permute', dato che non ho installato quello), ho ottenuto un aumento di velocità (e più scintille convertite) usando 'parListChunk k' invece di' parList' - anche con 'parListChunk 1', anche se questo chiama' parList'. –

risposta

7

Le strategie sono per l'esecuzione parallela di calcoli puri. Se è davvero obbligatorio che il valore f restituisca un valore IO, prendere in considerazione l'utilizzo del pacchetto async. Fornisce utili combinatori per l'esecuzione di azioni IO contemporaneamente.

Per il vostro caso d'uso, mapConcurrently sembra utile:

import Control.Concurrent.Async 

vals = [1..10] 
main = do 
    results <- mapConcurrently f vals 
    mapM_ print results 

(non ho ancora testato, però, perché non so che cosa il vostro f è esattamente.)

+0

Se * vuoi * testarlo, ho aggiunto il pieno script alla fine del mio post. In caso contrario: Async soffre dello stesso colpo di prestazioni, ma è un pacchetto che sembra molto promettente che ho trascurato finora, quindi grazie mille! – David

2

provare il pacchetto di parallel-io. Ti permette di cambiare qualsiasi mapM_ in parallel_.