Haskell Language
Paralelismo
Buscar..
Parámetros
Tipo / Función | Detalle |
---|---|
data Eval a | Eval es una mónada que facilita la definición de estrategias paralelas. |
type Strategy a = a -> Eval a | Una función que encarna una estrategia de evaluación paralela. La función atraviesa (parte de) su argumento, evaluando subexpresiones en paralelo o en secuencia |
rpar :: Strategy a | Chispea su argumento (para su evaluación en paralelo) |
rseq :: Strategy a | Evalúa su argumento a la forma normal de cabeza débil. |
force :: NFData a => a -> a | evalúa toda la estructura de su argumento, reduciéndolo a su forma normal, antes de devolver el argumento en sí. Es proporcionado por el módulo Control.DeepSeq |
Observaciones
El libro de Simon Marlow , Programación concurrente y paralela en Haskell, es sobresaliente y abarca una multitud de conceptos. También es muy accesible incluso para el programador de Haskell más nuevo. Es altamente recomendable y está disponible en PDF o en línea de forma gratuita.
Paralelo vs Concurrente
Simon Marlow lo pone mejor :
Un programa paralelo es uno que utiliza una multiplicidad de hardware computacional (por ejemplo, varios núcleos de procesador) para realizar un cálculo más rápidamente. El objetivo es llegar a la respuesta anterior, delegando diferentes partes de la computación a diferentes procesadores que se ejecutan al mismo tiempo.
Por el contrario, la concurrencia es una técnica de estructuración de programas en la que hay múltiples hilos de control. Conceptualmente, los hilos de control se ejecutan “al mismo tiempo”; Es decir, el usuario ve sus efectos intercalados. Si realmente se ejecutan al mismo tiempo o no es un detalle de implementación; un programa concurrente puede ejecutarse en un solo procesador a través de la ejecución intercalada o en múltiples procesadores físicos.
Forma normal de la cabeza débil
Es importante ser consciente de cómo funciona la evaluación perezosa. La primera sección de este capítulo brindará una sólida introducción a WHNF y cómo esto se relaciona con la programación paralela y concurrente.
La Mónada Eval
El paralelismo en Haskell se puede expresar usando la Mónada Eval
de Control.Parallel.Strategies
, usando las funciones rpar
y rseq
(entre otras).
f1 :: [Int]
f1 = [1..100000000]
f2 :: [Int]
f2 = [1..200000000]
main = runEval $ do
a <- rpar (f1) -- this'll take a while...
b <- rpar (f2) -- this'll take a while and then some...
return (a,b)
Ejecutar main
anterior ejecutará y "regresará" inmediatamente, mientras que los dos valores, a
y b
se computan en segundo plano a través de rpar
.
Nota: asegúrese de compilar con -threaded
para que se produzca la ejecución paralela.
rpar
rpar :: Strategy a
ejecuta la estrategia dada (recuerda: type Strategy a = a -> Eval a
) en paralelo:
import Control.Concurrent
import Control.DeepSeq
import Control.Parallel.Strategies
import Data.List.Ordered
main = loop
where
loop = do
putStrLn "Enter a number"
n <- getLine
let lim = read n :: Int
hf = quot lim 2
result = runEval $ do
-- we split the computation in half, so we can concurrently calculate primes
as <- rpar (force (primesBtwn 2 hf))
bs <- rpar (force (primesBtwn (hf + 1) lim))
return (as ++ bs)
forkIO $ putStrLn ("\nPrimes are: " ++ (show result) ++ " for " ++ n ++ "\n")
loop
-- Compute primes between two integers
-- Deliberately inefficient for demonstration purposes
primesBtwn n m = eratos [n..m]
where
eratos [] = []
eratos (p:xs) = p : eratos (xs `minus` [p, p+p..])
Ejecutando esto demostrará el comportamiento concurrente:
Enter a number
12
Enter a number
Primes are: [2,3,5,7,8,9,10,11,12] for 12
100
Enter a number
Primes are: [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100] for 100
200000000
Enter a number
-- waiting for 200000000
200
Enter a number
Primes are: [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200] for 200
-- still waiting for 200000000
rseq
Podemos usar rseq :: Strategy a
para forzar un argumento a una forma normal de cabeza débil:
f1 :: [Int]
f1 = [1..100000000]
f2 :: [Int]
f2 = [1..200000000]
main = runEval $ do
a <- rpar (f1) -- this'll take a while...
b <- rpar (f2) -- this'll take a while and then some...
rseq a
return (a,b)
Esto cambia sutilmente la semántica del ejemplo rpar
; mientras que el último sería volver de inmediato, mientras que el cálculo de los valores en el fondo, este ejemplo esperará hasta que a
se puede evaluar a WHNF.