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.


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.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.