Parallelo "qualsiasi" o "tutto" in Haskell


9

Un modello che ho incontrato diverse volte è quello in cui è necessario controllare un elenco di valori mappando un test su di esso e vedendo se uno o tutti gli elementi sono passati. La soluzione tipica è solo quella di utilizzare i comodi built-in alle any.

Il problema è che questi valutano in seriale. In molti casi sarebbe molto più veloce valutare parallelamente al completamento del processo una volta che un thread trova un "False" per allo un "True" per any. Sono abbastanza sicuro che il comportamento dei cortocircuiti non può essere implementato usando Control.Parallel in quanto richiede una comunicazione tra processi e non capisco abbastanza vicino a Control.Concurrent per implementarlo ancora.

È un modello abbastanza comune in matematica (ad es. Miller-Rabin Primality), quindi mi sento come se qualcuno avesse probabilmente già trovato una soluzione per questo, ma per ovvi motivi facendo una ricerca su Google per "parallelo o / e / qualsiasi / tutto in elenco haskell "non restituisce molti risultati rilevanti.


1
È possibile trovare utile la programmazione parallela e simultanea in Haskell , in particolare i capitoli 2 , 3 e 4 .
Brad

2
Questo è possibile con la unambbiblioteca
luqui l'

1
@luqui Fascinating; Mi occuperò di questo. Se scrivo un buon parallelo tutto / uno con questo, lo posterò come risposta.
Arcuritech,

11
Prima di provare a parallelizzare qualsiasi cosa, considera quante condizioni puoi testare nel tempo necessario per creare un nuovo processo.
Chepner,

2
@chepner di cosa stai parlando? Non stiamo parlando di bash qui! Possiamo fare concorrenza e parallelismo con i thread (che si tratti di thread pthreadsC o verdi in Haskell) Non devi avviare più server web per gestire le richieste web simultanee, invece esegui più thread in un unico processo! Lo stesso vale per il parallelismo. Raccogli quanti thread hai CPU e dividi il tuo lavoro in modo uniforme, occupandoti così delle attività legate alla CPU. Prova questa libreria per convincerti github.com/lehins/haskell-scheduler
lehins

Risposte:


2

In molti programmi realistici, è possibile utilizzare strategie parallele per questo scopo. Questo perché, anche se non esiste un meccanismo esplicito per annullare i calcoli non necessari, ciò accadrà implicitamente quando viene eseguito il Garbage Collector. A titolo di esempio concreto, considerare il seguente programma:

import Control.Concurrent
import Control.Parallel.Strategies
import Data.Int
import System.Mem

lcgs :: Int32 -> [Int32]
lcgs = iterate lcg
  where lcg x = 1664525 * x + 1013904223

hasWaldo :: Int32 -> Bool
hasWaldo x = waldo `elem` take 40000000 (lcgs x)

waldo :: Int32
waldo = 0

main :: IO ()
main = do
  print $ or (map hasWaldo [1..100] `using` parList rseq)

Questo utilizza una strategia di elenco parallelo per cercare waldo = 0(che non sarà mai trovato) nell'output di 100 flussi PRNG di 40 milioni di numeri ciascuno. Compilalo ed eseguilo:

ghc -threaded -O2 ParallelAny.hs
./ParallelAny +RTS -s -N4

e picchia quattro core per circa 16 secondi, alla fine stampa False. Nota nelle statistiche che tutte e 100 le scintille vengono "convertite" e quindi eseguite fino al completamento:

SPARKS: 100(100 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

Ora, waldopassa a un valore che può essere trovato in anticipo:

waldo = 531186389   -- lcgs 5 !! 50000

e modifica mainper mantenere attivo il thread per 10 secondi:

main :: IO ()
main = do
  print $ or (map hasWaldo [1..100] `using` parList rseq)
  threadDelay 10000000

Noterai che stampa Truequasi immediatamente, ma 4 core rimangono fissati al 100% della CPU (almeno per un po '), illustrando che i calcoli non necessari continuano a funzionare e non sono in cortocircuito, proprio come potresti aver temuto.

MA , le cose cambiano se si forza una raccolta dei rifiuti dopo aver ottenuto la risposta:

main :: IO ()
main = do
  print $ or (map hasWaldo [1..100] `using` parList rseq)
  performGC
  threadDelay 10000000

Ora vedrai che la CPU si disattiva poco dopo la stampa Truee le statistiche mostrano che la maggior parte dei calcoli sono stati raccolti in modo inutile prima dell'esecuzione:

SPARKS: 100(9 converted, 0 overflowed, 0 dud, 91 GC'd, 0 fizzled)

Nei programmi realistici, performGCnon sarà necessario un esplicito , poiché i GC saranno eseguiti regolarmente come una cosa ovvia. Alcuni calcoli non necessari continueranno a essere eseguiti dopo aver trovato la risposta, ma in molti scenari realistici, la frazione dei calcoli non necessari non sarà un fattore particolarmente importante.

In particolare, se l'elenco è ampio e ogni singolo test di un elemento di elenco è veloce, le strategie parallele avranno eccellenti prestazioni nel mondo reale ed è facile da implementare nell'affare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.