• Nenhum resultado encontrado

Haskell Paralelo (e entrada e saída de dados)

N/A
N/A
Protected

Academic year: 2021

Share "Haskell Paralelo (e entrada e saída de dados)"

Copied!
76
0
0

Texto

(1)

Haskell Paralelo

(e entrada e saída de dados)

Prof. Fabrício Olivetti de França Universidade Federal do ABC

(2)
(3)

Nós já conhecemos o comando print, que imprime qualquer valor na tela.

Um comando menos genérico é o putStrLn.

(4)

Ele recebe uma String e retorna um IO ()?

Imprimindo na tela

Prelude> :t putStrLn putStrLn :: String -> IO ()

GHCI

(5)

IO é o envelope da classe de entrada e saída de dados (Point, Just, …).

() representa uma unit, ou uma tupla sem elementos.

Vamos convencionar que IO é uma ação e IO () é uma ação que é executada e não retorna nada.

(6)

Da mesma forma temos a função getLine.

Ela espera por uma ação do usuário e armazena em name.

Imprimindo na tela

main = do

putStrLn "Please enter your name:"

name <- getLine

(7)

Diferente do que vimos até então, ela não

recebe parâmetros de entrada, apenas retorna uma String envolvida em IO.

Imprimindo na tela

Prelude> :t getLine

getLine :: IO String

(8)

Ações

Essas funções que recebem ou retornam IO são chamadas de ações pois queremos que sejam executadas no instante em que a

chamamos.

O retorno de um IO não pode ser previsto em tempo de compilação, não sabemos o que vai acontecer.

(9)

Ações

Considere a sequência: x <- getLine y <- getLine e: x = 13 y = 15

(10)

Ações

Na segunda sequência, não importa a ordem de operação, mas na primeira a ordem importa. O operador <- executa a ação naquele

instante, garantindo a coerência do programa, e remove o envelope da ação.

(11)

Blocos "do"

Toda vez que precisamos executar sequências de operações (de modo imperativo), utilizamos um bloco do:

do

nome <- getLine putStrLn nome

(12)

Blocos "do"

O bloco do não pode terminar com uma operação <-, pois deve terminar com uma instrução pura.

do

nome <- getLine putStrLn nome

(13)

Purificando

Não se esqueça de que, se formos utilizar os valores provenientes do envelope IO, temos que purificá-los!

do

linha <- getLine

let x = read linha :: Integer -- converte em int, se possível

(14)

Lendo arquivos

Imagine o seguinte arquivo de dados, exemploData.txt: 1.2 3.5 2.3

4.1 2.1 3.4 ...

(15)

Lendo arquivos

Queremos ler seu conteúdo e transformar em uma lista de listas:

(16)

Lendo arquivos

Lê o arquivo em FilePath e retorna ele como string (envolvido em um IO).

file <- readFile "arquivo"

file se torna uma String com o conteúdo do arquivo.

(17)

Lendo arquivos

Vamos criar uma função parseFile que fará a conversão, a assinatura dela deve ser:

(18)

Lendo arquivos

Queremos que cada linha do arquivo seja uma lista de Doubles:

parseFile :: String -> [Double]

(19)

Lendo arquivos

A função parseLine converte cada palavra da linha em um Double:

parseFile :: String -> [Double]

parseFile file = map parseLine (lines file) where

parseLine l = map toDouble (words l) toDouble w = read w :: Double

(20)
(21)

Vamos verificar como a avaliação preguiçosa funciona no Haskell.

Para isso utilizaremos a função sprint no ghci que mostra o estado atual da variável.

(22)

Quero evitar a fadiga

GHCI

Prelude> :set -XMonomorphismRestriction

Prelude> x = 5 + 10

Prelude> :sprint x x = _

(23)

Quero evitar a fadiga

GHCI

Prelude> x = 5 + 10 Prelude> :sprint x x = _ Prelude> x 3 Prelude> :sprint x x = 3

(24)

O valor de x é computado apenas quando requisitamos seu valor!

(25)

Quero evitar a fadiga

Prelude> x = 1 + 1 Prelude> y = x * 3 Prelude> :sprint x x = _ Prelude> :sprint y y = _

(26)

Quero evitar a fadiga

Prelude> x = 1 + 1 Prelude> y = x * 3 Prelude> :sprint x x = _ Prelude> :sprint y y = _ Prelude> y 6 Prelude> :sprint x x = 2

(27)

A função seq recebe dois parâmetros, avalia o primeiro e retorna o segundo.

(28)

Eu quero agora!

Prelude> x = 1 + 1 Prelude> y = 2 * 3 Prelude> :sprint x x = _ Prelude> :sprint y y = _ Prelude> seq x y 6 Prelude> :sprint x x = 2

(29)

Quero evitar a fadiga

Prelude> let l = map (+1) [1..10] :: [Int] Prelude> :sprint l l = _ Prelude> seq l () Prelude> :sprint l l = _ : _ Prelude> length l Prelude> :sprint l l = [_,_,_,_,_,_,_,_,_,_] Prelude> sum l Prelude> :sprint l l = [2,3,4,5,6,7,8,9,10,11]

(30)
(31)

Considere a implementaçõa ingênua de fibonacci:

Um ponto óbvio para paralelizar: enquanto uma thread trabalha no (n-1) outra no (n-2).

O óbvio

fib 0 = 0

fib 1 = 1

fib 2 = 1

(32)

Anotando paralelismo

import Control.Parallel

fib :: Integer -> Integer fib 0 = 0 fib 1 = 1 fib n = n1 `par` (n1 + n2) where n1 = fib (n - 1) n2 = fib (n - 2) main = do print (fib 36)

GHC

(33)

A função par indica para criar um spark para o primeiro argumento e executar o segundo

argumento. par x (x+y)

cria uma thread para calcular x e calcula x+y em paralelo.

(34)

Um spark é uma possibilidade de se tornar uma thread.

Se o programa julgar necessário transforma em thread.

(35)

Compile com:

ghc -o nome nome.hs -threaded -eventlog -rtsopts Execute com:

./nome +RTS -N1 -s -ls -M2g

(36)

-threaded: compile com suporte a multithreading -eventlog: permite criar um log do uso de threads -rtsopts: embute opções no seu programa

+RTS: flag para indicar opções embutidas -Nx: quantas threads usar

-s: estatísticas de execução

-ls: gera log para o threadscope

-M2g: limita o uso de memória em 2GB

(37)

Com 1 thread:

Total time 2.591s ( 2.620s elapsed) Com 2 threads:

Total time 2.749s ( 1.388s elapsed) O valor entre parênteses é o tempo real.

(38)

Anotando paralelismo

import Control.Parallel

fib :: Integer -> Integer fib 0 = 0 fib 1 = 1 fib n = n1 `par` (n2 + n1) where n1 = fib (n - 1) n2 = fib (n - 2) main = do print (fib 36)

GHC

(39)

Com 1 thread:

Total time 2.518s ( 2.541s elapsed) Com 2 threads:

Total time 4.475s ( 2.841s elapsed) O que houve??

(40)

No ghc o operador + avalia o operando da direita primeiro.

Enquanto uma thread avaliava n1 a principal avaliava também n1.

(41)

Anotando paralelismo

import Control.Parallel

fib :: Integer -> Integer fib 0 = 0

fib 1 = 1

fib n = n1 `par` n2 `pseq` (n2 + n1) where n1 = fib (n - 1) n2 = fib (n - 2) main = do print (fib 36)

GHC

(42)

pseq funciona como seq, porém aguarda que as threads terminem antes de continuar.

(43)

Com 1 thread:

Total time 2.845s ( 2.872s elapsed) Com 2 threads:

Total time 2.995s ( 1.523s elapsed)

Com o sincronismo, perdemos um tantinho de tempo, mas temos o paralelismo garantido.

(44)
(45)

Para avaliarmos se nossa estratégia de paralelismo está funcionando, temos a ferramenta threadscope:

$ cabal install threadscope

(46)

Fibonacci

(47)

Fibonacci

$ threadscope FibParSeq.eventlog

# de threads

Atividade thread 1 Atividade thread 2

(48)

Fibonacci

(49)

Vida de um spark

(50)

duds e fizzles

dud: já foi avaliado antes de virar uma thread fizzle: já foi avaliado por outra thread

(51)

Vida de um spark

Sinais de problemas:

- Poucos sparks, pode ser paralelizado ainda mais - Muitos sparks, paralelizando demais

(52)

Threadscope

Em breve veremos como usar o threadscope para melhorar nosso paralelismo.

(53)
(54)

Forma genérica de anotar os pontos a serem paralelizados.

Permite aplicar paralelismo em tipos compostos.

(55)

como paralelizar usando `par`?

Estratégias de Paralelismo

(56)

como paralelizar usando `par`?

Estratégias de Paralelismo

(57)

feio!

Estratégias de Paralelismo

(58)

Funções paralelas

parPair :: Strategy (a,b) parPair (a,b) = do

a' <- rpar a b' <- rpar b return (a',b')

(59)

Tipo Strategy de um tipo qualquer a é uma

função que recebe a e retorna a envelopado no tipo Eval.

O tipo Eval é um tipo que define o que e como deve ser avaliado.

(60)

rpar é uma função que retorna uma ação

envelopada em Eval (lembram de IO?).

Nesse caso precisamos utilizar a função return que envolve a tupla envelopada em Eval.

(61)

runEval executa a ação e retorna o valor fora

do envelope Eval.

Funções paralelas

(62)

Funções paralelas

using :: a -> Strategy a -> a x `using` s = runEval (s x)

(63)

Bonito! :)

Funções paralelas

(64)

parList, rseq, rdeepseq

parList - paraleliza o processamento de uma lista,

requer uma outra estratégia a ser aplicada em cada elemento.

rseq - força a avaliação de uma expressão dentro

daquele spark.

(65)
(66)

Cada elemento de l cria um spark que será avaliado via rseq.

Média

GHC

mean :: [[Double]] -> [Double]

mean l = map mean' l `using` parList rseq where

(67)

threadscope

Total time 1.381s ( 1.255s elapsed)

(68)

threadscope

Começamos a criar sparks muito rapidamente! Não deu tempo de aproveitá-los!

(69)

threadscope

Com o pool cheio, não deu tempo de enviar para o outro core! Então ficaram no core

(70)

Vamos tirar a estratégia...

Média

GHC

mean :: [[Double]] -> [Double]

mean l = map mean' l where

(71)

Agora dividimos a lista em pedaços de 1000 elementos e paralelizamos nesses pedaços.

Média

GHC

meanPar :: [[Double]] -> [Double]

meanPar l = concat lists where

lists = map mean chunks `using` parList rseq chunks = chunksOf 1000 l

(72)

threadscope

Total time 1.289s ( 1.215s elapsed)

(73)

threadscope

A aplicação da função mean foi preguiçosa, os sparks faziam apenas a promessa e

(74)

Vamos usar a estratégia rdeepseq.

Média

GHC

meanPar :: [[Double]] -> [Double]

meanPar l = concat lists where

lists = map mean chunks `using` parList rdeepseq chunks = chunksOf 1000 l

(75)

threadscope

Total time 1.303s ( 0.749s elapsed)

(76)

threadscope

Referências

Documentos relacionados

Para tratar a diversidade, a complexidade, e as diferentes formas de operações de cada periférico, as interfaces empregam no seu projeto um outro componente, o

A dissertação que originou essa Sequência Didática como Produto Educacional, tem como foco principal analisar a contribuição dos espaços não-formais mapeados em Vila

Esta publicação pretende ser um ponto de partida para suas próprias ideias e dispositivos, como uma ferramenta que atua em diferentes contextos e lugares inserindo,

Os dados serão adicionados no fim do arquivo (“append“) se ele já existir, ou um novo arquivo será criado, no caso de arquivo não existente anteriormente. • “rb“: Abre

No entanto, as perdas ocasionadas na cultura do milho em função da interferência imposta pelas plantas daninhas têm sido descritas como sendo da ordem de 13,1%, sendo que em casos

Na imagem abai- xo, por exemplo, as dimensões e o posicionamento dos personagens (Tio Sam e Cardoso) traduzem, em linguagem simples, o desnível geopolítico existente entre Brasil

 Se a comemoração do aniversário for realizada fora do ambiente escolar e os pais desejarem convidar todos os colegas de sala, sem exceção, poderão enviar o convite preenchido e

Nas leituras de falhas efetuadas, foram obtidos códigos de anomalia por meio de dois diferentes protocolos de comunicação: o ISO 14230 KWP (2000) e o ISO 15765-4 CAN. A seguir, no