Paralelo "cualquiera" o "todos" en Haskell

9

Un patrón que he encontrado varias veces ahora es uno en el que se debe verificar una lista de valores mapeando alguna prueba sobre él y ver si alguno o todos los elementos pasaron. La solución típica es usar los convenientes elementos integrados ally any.

El problema es que estos se evalúan en serie. En muchos casos, sería mucho más rápido evaluar en paralelo con el proceso completo una vez que cualquier hilo encuentre un "Falso" para allo un "Verdadero" para any. Estoy bastante seguro de que el comportamiento de cortocircuito no se puede implementar usando Control. Paralelo, ya que requiere comunicación entre procesos y no entiendo lo suficiente de Control.Concurrent para implementar esto todavía.

Es un patrón bastante común en matemáticas (por ejemplo, Miller-Rabin Primality), por lo que siento que alguien probablemente ya ha encontrado una solución para esto, pero por razones obvias haciendo una búsqueda en Google de "paralelo o / y / cualquiera / todo en la lista haskell "no devuelve muchos resultados relevantes.

Arcuritech
fuente
1
Puede encontrar útil la programación paralela y concurrente en Haskell , particularmente los capítulos 2 , 3 y 4 .
bradrn
2
Esto es posible con la unambbiblioteca
luqui
1
@luqui Fascinante; Voy a perder el tiempo con esto. Si escribo un buen paralelo all / any con esto, lo publicaré como respuesta.
Arcuritech
11
Antes de intentar paralelizar cualquier cosa, considere cuántas condiciones puede probar en el tiempo que lleva bifurcar un nuevo proceso.
chepner
2
@chepner, ¿de qué estás hablando? ¡No estamos hablando de bash aquí! Podemos hacer concurrencia y paralelismo con subprocesos (ya sea pthreadsen C o subprocesos verdes en Haskell) ¡Usted no inicia múltiples servidores web para manejar solicitudes web concurrentes, en su lugar ejecuta múltiples subprocesos en un solo proceso! Lo mismo se aplica al paralelismo. Gira tantos subprocesos como CPU y divide su trabajo de manera uniforme, por lo que se encarga de las tareas vinculadas a la CPU. Pruebe esta biblioteca para convencerse a sí mismo github.com/lehins/haskell-scheduler
lehins

Respuestas:

2

En muchos programas realistas, puede usar estrategias paralelas para este propósito. Esto se debe a que, aunque no existe un mecanismo explícito para cancelar los cálculos innecesarios, esto sucederá implícitamente cuando se ejecute el recolector de basura. Como ejemplo concreto, considere el siguiente programa:

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)

Utiliza una estrategia de lista paralela para buscar waldo = 0(que nunca se encontrará) en la salida de 100 flujos PRNG de 40 millones de números cada uno. Compilar y ejecutarlo:

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

y fija cuatro núcleos durante unos 16 segundos, eventualmente imprimiendo False. Tenga en cuenta en las estadísticas que las 100 chispas se "convierten" y se ejecutan hasta su finalización:

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

Ahora, cambie waldoa un valor que se pueda encontrar temprano:

waldo = 531186389   -- lcgs 5 !! 50000

y modifique mainpara mantener vivo el hilo durante 10 segundos:

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

Observará que se imprime Truecasi de inmediato, pero 4 núcleos permanecen vinculados al 100% de la CPU (al menos durante un tiempo), lo que ilustra que los cálculos innecesarios siguen ejecutándose y no están en cortocircuito, como podría haber temido.

PERO , las cosas cambian si fuerza una recolección de basura después de obtener la respuesta:

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

Ahora, verá que la CPU queda inactiva poco después de la impresión True, y las estadísticas muestran que la mayoría de los cálculos se recolectaron como basura antes de ejecutarse:

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

En los programas realistas, performGCno se necesitará un mensaje explícito , ya que los GC se realizarán regularmente de forma habitual. Algunos cálculos innecesarios continuarán ejecutándose después de encontrar la respuesta, pero en muchos escenarios realistas, la fracción de cálculos innecesarios no será un factor particularmente importante.

En particular, si la lista es grande y cada prueba individual de un elemento de la lista es rápida, las estrategias paralelas tendrán un excelente rendimiento en el mundo real y es fácil de implementar en el negocio.

KA Buhr
fuente