Paralelo "qualquer" ou "tudo" em Haskell

9

Um padrão que me deparei várias vezes agora é aquele em que uma lista de valores precisa ser verificada mapeando algum teste sobre ele e verificando se algum ou todos os elementos foram aprovados. A solução típica é apenas usar os convenientes embutidos alle any.

O problema é que eles são avaliados em série. Em muitos casos, seria muito mais rápido avaliar em paralelo com o processo sendo concluído quando qualquer encadeamento encontrar um "False" para allou um "True" para any. Tenho certeza de que o comportamento em curto-circuito não pode ser implementado usando Control.Parallel, pois requer comunicação entre processos e não entendo em lugar algum o Control.Concurrent para implementar isso ainda.

É um padrão bastante comum em matemática (por exemplo, Miller-Rabin Primality), então eu sinto que alguém provavelmente já encontrou uma solução para isso, mas por razões óbvias fazendo uma pesquisa no google por "paralelo ou / e / qualquer / tudo na lista" haskell "não retorna muitos resultados relevantes.

Arcuritech
fonte
11
Você pode achar útil a programação paralela e simultânea em Haskell , principalmente os capítulos 2 , 3 e 4 .
bradrn
2
Isso é possível com a unambbiblioteca
luqui 11/02
11
@luqui Fascinante; Eu vou mexer com isso. Se eu escrever um bom paralelo com tudo isso, postarei como resposta.
Arcuritech
11
Antes de tentar paralelizar qualquer coisa, considere quantas condições você pode testar no tempo necessário para bifurcar um novo processo.
chepner 11/02
2
@chepner do que você está falando? Não estamos falando de festança aqui! Podemos fazer simultaneidade e paralelismo com threads (seja pthreadsem C ou em verde em Haskell). Você inicia vários servidores da web para lidar com solicitações simultâneas da web, em vez de executar vários threads em um único processo! O mesmo se aplica ao paralelismo. Você gira o número de threads que possui CPUs e divide seu trabalho uniformemente, cuidando das tarefas ligadas à CPU. Tente esta biblioteca para se convencer github.com/lehins/haskell-scheduler
lehins

Respostas:

2

Em muitos programas realistas, você pode usar estratégias paralelas para esse fim. Isso ocorre porque, embora não exista um mecanismo explícito para cancelar cálculos desnecessários, isso acontecerá implicitamente quando o coletor de lixo for executado. Como um exemplo concreto, considere o seguinte 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)

Isso usa uma estratégia de lista paralela para procurar waldo = 0(que nunca será encontrada) na saída de 100 fluxos PRNG de 40 milhões de números cada. Compile e execute:

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

e prende quatro núcleos por cerca de 16s, eventualmente imprimindo False. Observe nas estatísticas que todas as 100 faíscas são "convertidas" e, portanto, executadas até a conclusão:

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

Agora, mude waldopara um valor que pode ser encontrado mais cedo:

waldo = 531186389   -- lcgs 5 !! 50000

e modifique mainpara manter o encadeamento ativo por 10 segundos:

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

Você observará que a impressora é impressa Truequase imediatamente, mas quatro núcleos permanecem fixados em 100% da CPU (pelo menos por um tempo), ilustrando que os cálculos desnecessários continuam em execução e não estão em curto-circuito, como você poderia temer.

MAS , as coisas mudam se você forçar uma coleta de lixo após obter a resposta:

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

Agora, você verá que a CPU fica ociosa logo após a impressão Truee as estatísticas mostram que a maioria dos cálculos foram coletados como lixo antes da execução:

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

Em programas realistas, um explícito performGCnão será necessário, pois os GCs serão realizados regularmente como uma questão de curso. Alguns cálculos desnecessários continuarão em execução após a resposta ser encontrada, mas em muitos cenários realistas, a fração de cálculos desnecessários não será um fator particularmente importante.

Em particular, se a lista for grande e cada teste individual de um elemento da lista for rápido, as estratégias paralelas terão excelente desempenho no mundo real e serão fáceis de implementar na barganha.

KA Buhr
fonte