Parallelität und Nebenläufigkeit mit Haskell

Werbung
Parallelität und Nebenläufigkeit mit Haskell
Stefan Wehr ([email protected])
factis research GmbH, Freiburg im Breisgau
16. Mai 2013
parallel2013, Karlsruhe
Parallelität und Nebenläufigkeit mit Haskell
1 / 68
Warum funktional?
I
I
I
Bewusster Umgang mit Seiteneffekten
Wenig globaler Zustand
Datenabhängigkeiten oft explizit
Parallelität und Nebenläufigkeit mit Haskell
2 / 68
Warum Haskell?
I
I
I
I
Ausdrucksstarkes Typsystem: Wenn das Programm kompiliert
funktioniert es auch!
Kurzer, prägnanter und lesbarer Code
Einfaches Abstrahieren
“World’s finest imperative programming language”
I
I
I
Unterschied zwischen Aktion (“Statements”) mit
Seiteneffekten und reinen Ausdrücken
Aktionen sind Werte erster Klasse: Eigene Kontrollstrukturen,
Programmatische Konstruktionen von Aktionen
Reichhaltige Palette an Ansätzen zur parallelen und
nebenläufigen Programmierung
I
I
I
I
I
Deterministischer Parallelismus
Datenparallelismus
GPU-Programmierung
STM
Leichtgewichtige Threads
Parallelität und Nebenläufigkeit mit Haskell
3 / 68
Mein Hintergrund
I
I
I
I
Haskell Benutzer seit 2003
Zunächst vor allem im akademischen Bereich
Seit 2010: Anwendung von Haskell in der Industrie
factis research GmbH, Freiburg im Breisgau
I
I
I
I
Softwareprodukte für den Medizin- und Pflegebereich
Serverseitige Software fast ausschließlich in Haskell geschrieben
Große Erfahrung mit komplexen mobilen Anwendungen
Projekte und Schulungen im Bereich funktionale
Programmierung und mobile Anwendungen
Parallelität und Nebenläufigkeit mit Haskell
4 / 68
Nebenläufigkeit
I
Nebenläufigkeit ist ein eigenständiges Ziel
I
I
I
Gleichzeitige Kommunikation mit mehreren externen Quellen
Reaktion auf Events der Außenwelt
Notwendigerweise nicht-deterministisch
Schwierigkeiten
I
I
I
I
Deadlocks
Race conditions
Nicht-deterministisches Verhalten
Testabdeckung
Parallelität und Nebenläufigkeit mit Haskell
5 / 68
Vorteile von Nebenläufigkeit
I
I
Manche Probleme werden durch Nebenläufigkeit auch
einfacher
Beispiel: Netzwerkserver mit einem Thread pro Client
I
I
Sequentieller Ablauf pro Client
Alternative: Gleichzeitige Interaktion mit allen Clients
I
Komplizierte Zustandsmaschine
Parallelität und Nebenläufigkeit mit Haskell
6 / 68
Parallelität
I
I
I
Parallelität ist kein Ziel per se
Ziel: Programm soll schneller laufen
Parallelität ist ein möglicher Weg dieses Ziel zu erreichen
I
I
I
Benutzung mehrerer CPU Kerne
Benutzung von GPU Kernen
Kann determinisisch sein
Parallelität und Nebenläufigkeit mit Haskell
7 / 68
Schwierigkeiten bei der Parallelisierung
I
I
I
Welche Teile des Programms sind langsam?
Welche Teile des Programms können parallelisiert werden?
Datenabhängigkeiten?
Granularität
I
I
Zu fein: Overhead wird zu groß
Zu grob: nicht genug Parallelität möglich
Parallelität und Nebenläufigkeit mit Haskell
8 / 68
Einführung in Haskell
I
Haskell ist eine statische getypte Programmiersprache
I
I
I
I
I
Haskell ist eine funktionale Programmiersprache
I
I
I
Jeder Ausdruck hat zur Kompilierzeit einen Typ
Typsystem erlaubt Differenzierung nach Seiteneffekten
Typen müssen nicht explizit hingeschrieben werden
(Typinferenz)
Gute Angewohnheit: Schreiben Typsignaturen an top-level
Funktionen
Funktionen nehmen eine zentral Rolle ein
Funktionen als Parameter und Rückgabewerte anderer
Funktionen
Lazy Auswertung
I
Ausdrücke werden erst dann ausgewertet wenn ihr Ergebnis
gebraucht wird
Parallelität und Nebenläufigkeit mit Haskell
9 / 68
Funktionen in Haskell
I
Ohne Seiteneffekte
formatAge :: String -> Int -> String
formatAge name age =
name ++ " ist " ++ show age ++ " Jahre alt!"
*Main> formatAge "Stefan" 34
"Stefan ist 34 Jahre alt!"
I
Mit Seiteneffekten
sayAge :: String -> IO ()
sayAge name =
do putStrLn ("Wie alt ist " ++ name ++ "? ")
ageStr <- getLine
putStrLn (formatAge name (read ageStr))
Parallelität und Nebenläufigkeit mit Haskell
10 / 68
Listen in Haskell
I
Eine Liste . . .
I
I
I
. . . ist entweder leer: []
. . . oder besteht aus einem Element x und einer Restliste
rest: x : rest
Typ einer Liste: [a] wobei a der Typ der Listenelemente ist
countdown :: [Int]
countdown =
10 : 9 : 8 : 7 : 6 : 5 : 4 : 3 : 2 : 1 : 0 : []
zutaten :: [String]
zutaten = ["Nudeln", "Tomaten", "Salz"]
Parallelität und Nebenläufigkeit mit Haskell
11 / 68
Rekursive Funktionen in Haskell
incList :: [Int] -> [Int]
incList []
= []
incList (x:rest) = x + 1 : incList rest
*Main> incList countdown
[11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
incList’ :: [Int] -> [Int]
incList’ l = map (\x -> x + 1) l
map :: (a -> b) -> [a] -> [b]
map f []
= []
map f (x:rest) = f x : map f rest
Parallelität und Nebenläufigkeit mit Haskell
12 / 68
Typparameter und Constraints in Haskell
I
I
I
I
map :: (a -> b) -> [a] -> [b] ist generisch (oder
polymorph) in den Typparametern a und b
Constraints schränken Typparameter ein und machen gewissen
Funktionalitäten verfügbar
Constraint-System ist erweiterbar
Beispiel: Gleichheitsoperator == nur verfügbar für Typen a mit
Constraint Eq a
elem :: Eq a => a -> [a] -> Bool
elem y []
= False
elem y (x:rest) = if x == y then True else elem y rest
Parallelität und Nebenläufigkeit mit Haskell
13 / 68
Monaden in Haskell
I
I
I
I
Programmierbares Semikolon
Abstraktion über Sequenzierung
do-Notation
IO ist eine Monade
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
mapM f []
= return []
mapM f (x:rest) =
do y <- f x
rest’ <- mapM f rest
return (y:rest’)
*Main> mapM sayAge ["Stefan", "Georg"]
I
Definition eigener Monaden möglich
Parallelität und Nebenläufigkeit mit Haskell
14 / 68
Deterministischer Parallelismus in Haskell
I
I
I
Die Par Monade
Explizite Datenflussinformationen durch Verwendung spezieller
“Boxen”
Semi-automatische Parallelisierung
Parallelität und Nebenläufigkeit mit Haskell
15 / 68
Beispiel: Sudoku
I
I
I
Parallelisierung durch gleichzeitiges Lösen mehrere Rätsel
Keine Parallelisierung des Algorithmus zum Lösen eines
Rätsels
Gegeben:
I
I
I
Funktion zum Lösen eines Rätsels: solve ::
Maybe Grid
data Maybe a = Just a | Nothing
String ->
Gesucht: Funktion zum Lösen mehrerer Rätsel:
computeSolutions :: [String] -> [Maybe Grid]
Parallelität und Nebenläufigkeit mit Haskell
16 / 68
Sequentielles Sudoku-Lösen
-- Datei: SudokuSeq.hs
computeSolutionsSeq :: [String] -> [Maybe Grid]
computeSolutionsSeq puzzles =
map solve puzzles
I
I
I
Kompilieren: ghc --make -O2 -threaded -rtsopts
SudokuSeq.hs
Ausführen: ./SudokuSeq +RTS -s -RTS
sudoku17.1000.txt
Laufzeit: Total time 1.52s ( 1.53s elapsed)
I
I
CPU-Zeit: 1,52 Sekunden
Wall-Zeit: 1,53 Sekunden
Parallelität und Nebenläufigkeit mit Haskell
17 / 68
Grundlagen der Par Monade
I
I
Installation: cabal install monad-par
Par a
I
I
fork ::
I
I
Typ einer Aktion in der Par-Monad mit Ergebnistyp a
Führt eine Aktion parallel aus
runPar ::
I
I
I
Par () -> Par ()
Par a -> a
Bringt Aktionen in der Par-Monade zurück in die Welt der
normale Haskell-Berechnungen.
Ohne Seiteneffekte (sieht man am Typ!)
Deterministisch (sieht man am Typ!)
Parallelität und Nebenläufigkeit mit Haskell
18 / 68
Datenfluss in der Par Monade
I
I
I
Parallele Aktionen kommunizieren über Boxen
IVar a: Box für Werte vom Typ a
Boxen beschreiben den Datenfluss zwischen Aktionen
I
I
I
I
new :: Par (IVar a)
get :: IVar a -> Par a
put :: NFData a => IVar a -> a -> Par ()
Wichtig: Jede Box darf nur einmal beschrieben werden, sonst
Aktion nicht mehr deterministisch
Parallelität und Nebenläufigkeit mit Haskell
19 / 68
Sudoku: statische Paritionierung mit Par
-- Datei: SudokuPar1.hs
computeSolutionsPar1 :: [String] -> [Maybe Grid]
computeSolutionsPar1 puzzles =
let n = length puzzles
(as, bs) = splitAt (n ‘div‘ 2) puzzles
in runPar (f as bs)
where
f :: [String] -> [String] -> Par [Maybe Grid]
f as bs = do b1 <- new
b2 <- new
fork (put b1 (map solve as))
fork (put b2 (map solve bs))
res1 <- get b1
res2 <- get b2
return (res1 ++ res2)
Parallelität und Nebenläufigkeit mit Haskell
20 / 68
Performance mit statischer Partitionierung
I
Laufzeit auf einem Kern:
I
I
I
Mit 2 Kernen: Laufzeitoption -N<K> mit K Anzahl der Kerne
I
I
I
./SudokuPar1 +RTS -s -RTS sudoku17.1000.txt
1,52s CPU-Zeit, 1,54s Wall-Zeit
./SudokuPar1 +RTS -s -N2 -RTS sudoku17.1000.txt
1,57s CPU-Zeit, 0,97s Wall-Zeit
Speedup: 1, 53/0, 97 = 1, 55
Parallelität und Nebenläufigkeit mit Haskell
21 / 68
Debugging mit Threadscope
I
I
I
Kompilieren: ghc --make -O2 -threaded -rtsopts
-eventlog -o SudokuPar1 e SudokuPar1.hs
Ausführen: ./SudokuPar1 e +RTS -s -N2 -ls -RTS
sudoku17.1000.txt
Eventlog in Threadscope öffnen
Parallelität und Nebenläufigkeit mit Haskell
22 / 68
Probleme mit statischer Partitionierung
I
Verschiedene Sudokurätsel brauchen unterschiedliche viel Zeit
zum Lösen
I
I
Statische Partitionierung teilt Liste der Problem einfach in der
Mitte
Statische Partitionierung legt sich auf zwei parallele
Berechnungen fest
I
Laufzeit mit vier Kernen schlechter als mit zwei Kernen
I
I
./SudokuPar1 +RTS -s -N4 -RTS sudoku17.1000.txt
1,80s CPU-Zeit, 1,03s Wall-Zeit
Parallelität und Nebenläufigkeit mit Haskell
23 / 68
Sudoku: dynamische Paritionierung
I
I
I
Skaliert auf beliebig viele Kerne
Kommt mit unterschiedlichen Größen der Teilprobleme zurecht
Idee:
I
I
Teile Liste der Sudokurätsel in viele kleine Einheiten
Laufzeitsystem kümmert sich um Aufteilung der Einheiten auf
Prozessorkerne
Parallelität und Nebenläufigkeit mit Haskell
24 / 68
Dynamische Partitionierung mit Par
I
Rückblick auf sequenzielle Variante:
computeSolutionsSeq puzzles = map solve puzzles
I
Parallele Variante von map
map
::
(a -> b) -> [a] ->
[b]
parMap :: NFData b => (a -> b) -> [a] -> Par [b]
I
Parallele Variante des Sudoku-Solvers
computeSolutionsPar2 :: [String] -> [Maybe Grid]
computeSolutionsPar2 puzzles =
runPar (parMap solve puzzles)
Parallelität und Nebenläufigkeit mit Haskell
25 / 68
Speedup mit dynamischer Partitionierung
Parallelität und Nebenläufigkeit mit Haskell
26 / 68
Implementierung von parMap
I
Idee:
I
I
I
parMap f xs wertet f auf jedes Element aus xs in einer
neuen, parallelen Berechung aus
Ergebnis einer solchen Berechnung landet in einer Box
Am Schluss werden alle Ergebnisse aus den Boxen
eingesammelt
parMap :: NFData b => (a -> b) -> [a] -> Par [b]
parMap f xs =
do bs <- mapM g xs
mapM get bs
where
g x =
do b <- new
fork (put b (f x))
return b
Parallelität und Nebenläufigkeit mit Haskell
27 / 68
Was ist Datenparallelismus?
I
I
I
Gleichzeitiges Anwenden derselben Operation auf
unterschiedliche Daten
SIMD-Instruktionen
Flacher Datenparallelismus:
I
I
I
I
Paralleles Anwenden sequentieller Operationen
Weitverbreitet (MPI, HPF, . . . )
Begrenzte Anwendungsmöglichkeiten
Geschachtelter Datenparallelismus:
I
I
Paralleles Anwenden paralleler Operationen
Sehr viele Anwendungsmöglichkeiten
I
I
I
I
I
I
Divide-And-Conquer Algorithmen
Graphenalgorithmen
Machine Learning
...
Modularer als flacher Datenparallelismus
Forschungsthema, Implementierung sehr schwierig
Parallelität und Nebenläufigkeit mit Haskell
28 / 68
Accelerate: flacher Datenparallelismus auf der GPU
I
I
I
GPUs sind parallele Mehrkernprozessoren
Spezialisiert auf parallele Grafikoperationen (flacher
Datenparallelismus)
GPGPU: General Purpose Computation on Graphics
Processing Unit
I
I
I
OpenCL, CUDA
Sehr aufwändig
Idee: Erzeuge aus Haskell Code ein GPU Programm
I
I
I
I
Accelerate ist eine EDSL (Embedded Domain Specific
Language)
Accelerate ist eine (kleine) Teilmenge von Haskell
Syntaxbaum der EDSL wird zur Laufzeit reifiziert und mittels
CUDA auf die GPU gebracht
Performance: etwas langsamer als natives CUDA, aber
akzeptabel
Parallelität und Nebenläufigkeit mit Haskell
29 / 68
Accelerate, ein Beispiel
import qualified Data.Array.Accelerate as A
dotp ::
->
->
dotp xs
let
A.Vector Float
A.Vector Float
A.Acc (A.Scalar Float)
ys =
xs’ = A.use xs
ys’ = A.use ys
in A.fold (+) 0 (A.zipWith (*) xs’ ys’)
I
I
A.use transferiert die Daten auf die GPU
A.fold und A.zipWith werden auf der GPU ausgeführt
Parallelität und Nebenläufigkeit mit Haskell
30 / 68
DPH: geschachtelter Datenparallelismus
I
I
I
I
I
Paralleles Anwenden paralleler Operationen
Viel flexibler und modularer als flacher Datenparallelismus
DPH: Data Parallel Haskell
Zentraler Begriff: parallele Arrays, z.B. [: Double :]
Operationen über parallele Arrays werden automatisch
parallelisiert
Parallelität und Nebenläufigkeit mit Haskell
31 / 68
DPH, ein Beispiel
I
I
I
I
Multiplikation dünnbesetzter Matrizen mit einem Vektor
[:Double:]: dichtbesetzer Vektor
[:(Int, Double):]: dünnbesetzter Vektor
Dünnbesetzte Matrix: [:[:(Int,Double):]:]
svMul :: [:(Int,Double):] -> [:Double:] -> Double
svMul sv v = sumP [: (v !: i) * f | (i,f) <- sv :]
smMul :: [:[:(Int,Double):]:] -> [:Double:] -> Double
smMul sm v = sumP [: svMul row v | row <- sm :]
Parallelität und Nebenläufigkeit mit Haskell
32 / 68
Nebenläufigkeit mit Haskell
I
Threads sind extrem billig
I
I
I
I
I
forkIO :: IO () -> IO ThreadId
Green Threads
Mehrere Million Threads auf diesem Laptop keine Problem
Gute Interaktion mit blockierenden Systemcalls und nativen
Threads
Synchronisation mit STM
I
I
Relativ einfach (im Gegensatz zu Locks)
Modular (im Gegensatz zu Locks)
Parallelität und Nebenläufigkeit mit Haskell
33 / 68
STM (Software Transactional Memory)
I
Beispiel: Überweisung von einem Bankkonto
I
I
I
I
Probleme mit Threads
I
I
I
Deadlocks, Race conditions
Unmodular
Idee von STM:
I
I
I
transfer acc1 acc2 amount: überweise Betrag amount von
Konto acc1 auf acc2
transfer soll thread-safe sein
Im Beispiel: Konten sind nicht persistenz
Markiere Codeblöcke als “atomar”
Atomare Blöcke werden entweder ganz oder gar nicht
ausgeführt (wie Datenbanktransaktionen)
Vorteile von Haskell:
I
I
Immutability
Lazy Auswertung
Parallelität und Nebenläufigkeit mit Haskell
34 / 68
Kontoüberweisungen mit STM
transfer :: Account -> Account -> Int -> IO ()
transfer acc1 acc2 amount =
atomically (do deposit acc2 amount
withdraw acc1 amount)
I
atomically ::
I
I
I
I
STM a -> IO a
Führt die übergebene Aktion atomar aus
Auszuführende Aktion wird auch Transaktion genannt
STM a: Typ einer Transaktion mit Ergebnis vom Typ a
STM ist eine Monade
Parallelität und Nebenläufigkeit mit Haskell
35 / 68
Transaktionsvariablen
I
STM-Aktionen kommunizieren über Transaktionsvariablen
I
I
I
I
TVar a: Transaktionsvariablen die Wert vom Typ a enthält
Lesen: readTVar :: TVar a -> STM a
Schreiben: writeTVar :: TVar a -> a -> STM ()
Außerhalb von atomically können Transaktionsvariablen
weder gelesen noch geschrieben werden.
type Account = TVar Int
deposit :: Account -> Int -> STM ()
deposit acc amount =
do bal <- readTVar acc
writeTVar acc (bal + amount)
withdraw :: Account -> Int -> STM ()
withdraw acc amount = deposit acc (- amount)
Parallelität und Nebenläufigkeit mit Haskell
36 / 68
Blockieren mit STM
I
I
Warten auf eine bestimmte Bedingung ist essential für
nebenläufige Programme
Beispiel: limitedWithdraw soll blockieren falls nicht genug
Geld zum Abheben da ist
limitedWithdraw :: Account -> Int -> STM ()
limitedWithdraw acc amount =
do bal <- readTVar acc
if amount > 0 && amount > bal
then retry
else writeTVar acc (bal - amount)
I
retry ::
I
I
STM a
Bricht die aktuelle Transaktion ab
Versucht zu einem späteren Zeitpunkt die Transaktion
nochmals auszuführen
Parallelität und Nebenläufigkeit mit Haskell
37 / 68
Kombinieren von Transaktionen
I
Beispiel: Abheben mittels limitedWithdraw von mehreren
Konto solange bis ein Konto genug Geld hat
limitedWithdrawMany :: [Account] -> Int -> STM ()
limitedWithdrawMany [] _ = retry
limitedWithdrawMany (acc:rest) amount =
limitedWithdraw acc amount ‘orElse‘
limitedWithdrawMany rest amount
I
orElse ::
I
I
I
I
STM a -> STM a -> STM a
orElse t1 t2 führt zuerst t1 aus
Falls t1 erfolgreich, so auch orElse t1 t2
Falls t1 abbricht (durch Aufruf von retry) wird t2 ausgeführt
Falls t2 abbricht bricht auch die gesamte Transaktion ab, d.h.
sie wird zu einem späteren Zeitpunkt nochmals ausgeführt
Parallelität und Nebenläufigkeit mit Haskell
38 / 68
Zusammenfassung der STM API
atomically :: STM a -> IO a
retry :: STM a
orElse :: STM a -> STM a -> STM a
newTVar
:: a -> STM (TVar a)
readTVar :: TVar a -> STM a
writeTVar :: TVar a -> a -> STM ()
Parallelität und Nebenläufigkeit mit Haskell
39 / 68
Ausführungsmodell für STM
I
I
I
I
I
atomically t wird optimistisch ausgeführt, ohne Locks
writeTVar v x schreibt in ein Log, nicht in den Speicher
readTVar v liest erst aus dem Log und nur bei Misserfolg aus
dem Speicher
Falls readTVar v den Wert nicht im Log findet wird der im
Speicher gelesene Wert auch im Log vermerkt
Am Ende einer Transaktion erfolgt die Validierung
I
I
I
I
Muss atomar erfolgen (Locks)
Validierung erfolgreich falls die Werte aller Leseoperation im
Log zum Hauptspeicherinhalt passen
Bei erfolgreicher Validierung: aktualisiere Hauptspeicher
retry benutzt Log um Herauszufinden wann eine
Neuausführung sich lohnt
Parallelität und Nebenläufigkeit mit Haskell
40 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
41 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
42 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
43 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
44 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
45 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
46 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
47 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
48 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
49 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
50 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
51 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
52 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
53 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
54 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
55 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
56 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
57 / 68
Beispiel: Ausführung von STM
Parallelität und Nebenläufigkeit mit Haskell
58 / 68
Typsystem verhindert STM-Katastrophen!
I
I
IO-Aktionen sind nicht beliebig wiederholbar
Typsystem verhindert Ausführung von IO-Aktionen innerhalb
einer STM-Transaktion
launchMissiles :: IO ()
launchMissiles = -- ...
bad xv yv =
atomically (do x <- readTVar xv
y <- readTVar yv
when (x > y) launchMissiles)
$ ghc Bad.hs
Bad.hs:12:25:
Couldn’t match type ‘IO’ with ‘STM’
Expected type: STM ()
Actual type: IO ()
Parallelität und Nebenläufigkeit mit Haskell
59 / 68
Threads in Haskell
I
Threads sind extrem billig
I
I
I
I
I
I
forkIO : IO () -> IO ThreadId
Green Threads
Platzbedarf < 100 Bytes + Stack
Mehrere Million Threads auf diesem Laptop keine Problem
Runtimesystem implementiert blockierende Aufrufe intern
asynchron (z.B. mit epoll unter Linux)
Interoperabilität mit nativem Code
I
I
Runtimesystem führt nativen Code in gesonderten OS-Threads
aus
forkOS :: IO () -> IO ThreadId
I
I
erstellt einen Haskell-Thread, dessen nativen Aufrufe immer
im selben OS-Thread laufen
Wichtig für bestimmt Bibliotheken und “thread-local State”
Parallelität und Nebenläufigkeit mit Haskell
60 / 68
Vergleich mit node.js
I
I
I
node.js: Javascript Framework für serverseitige
Netzwerkanwendungen
Bekannt für gute Performance
Asynchrone Schnittstelle zum Lesen von Sockets
I
I
“inversion of control”
Unbequem zum Programmieren
I
I
I
Logik für mehrere Clients vermischt
Zustand des Kontrollflusses muss explizit in globalen Variablen
abgelegt werden
Ausnutzung mehrerer Kerne nur durch mehrere Prozesse
möglich
I
Benutzung von in-memory Datenstrukturen über mehrere
Kerne hinweg schwierig
Parallelität und Nebenläufigkeit mit Haskell
61 / 68
Benchmark Haskell vs. node.js
I
I
I
I
Einfacher Server zum Verdoppeln von Zahlen
Client schickt eine Zahl, Server schickt das Doppelte der Zahl
zurück
Client kann mehrere Zahlen schicken bevor der Server eine
Antwort schickt
Beispiel:
Parallelität und Nebenläufigkeit mit Haskell
62 / 68
Haskell Server mit Conduits
I
Conduits
I
Lösung für das Streaming Problem
I
I
I
Konstanter Speicher
Prompte Freigabe von Resourcen
Prinzip: Unix-Pipes
main =
do let settings
runTCPServer
where
doubleApp x =
appSource
(CB.lines
appSink x
Parallelität und Nebenläufigkeit mit Haskell
= serverSettings 44444 HostAny
settings doubleApp
x $=
=$= processLine) $$
63 / 68
Die processLine Funktion
I
I
await: Wartet auf neue Daten, blockiert falls keine da.
sendLine: Erzeugt eine Ausgabe
processLine :: Monad m
=> Conduit BS.ByteString m BS.ByteString
processLine =
do mBs <- await -- blockiert bis Daten da
case mBs of
Nothing -> return () -- EOF
Just bs | bs == "end" -> sendLine "Thank you"
| otherwise ->
let l = case parseInt bs of
Just i -> show (2 * i)
Nothing -> "not an int"
in do sendLine l
processLine
Parallelität und Nebenläufigkeit mit Haskell
64 / 68
Benchmark-Ergebnisse
I
I
5000 parallele Clients
10 Anfragen pro Client
Parallelität und Nebenläufigkeit mit Haskell
65 / 68
Resourcen: Parallelität & Nebenläufigkeit
I
I
Folien und Material zum Vortrag:
http://factisresearch.com/parallel2013/
Parallel and Concurrent Programming in Haskell, Simon
Marlow
I
I
I
Beautiful concurrency, Simon Peyton Jones
I
I
I
http:
//community.haskell.org/~simonmar/par-tutorial.pdf
Buch erscheint in Kürze bei O’Reilly
appeared in “Beautiful code”, Herausgeber Greg Wilson,
O’Reilly 2007
http:
//research.microsoft.com/pubs/74063/beautiful.pdf
Deterministic Parallel Programming with Haskell, Duncan
Coutts und Andres Löh
I
I
Computing in Science & Engineering, Volume 14, Issue 6, Dez.
2012
http://www.well-typed.com/blog/aux/files/
Parallelität und Nebenläufigkeit mit Haskell
66 / 68
Resourcen: Haskell und funktionale Programmierung
allgemein
I
I
I
Haskell Homepage: http://haskell.org
School of Haskell: https://www.fpcomplete.com/
Learn You a Haskell for Great Good, Miran Lipovača, No
Starch Press, 2011
I
I
Real World Haskell, Bryan O’Sullivan, Don Stewart und John
Goerzen, O’Reilly 2008
I
I
I
http://learnyouahaskell.com/
http://book.realworldhaskell.org/
Programming in Haskell, Graham Hutton, Cambridge
University Press, 2007
Blog Funktionale Programmierung:
http://funktionale-programmierung.de/
Parallelität und Nebenläufigkeit mit Haskell
67 / 68
Fazit & Zusammenfassung
I
Haskell bietet viel bzgl. Nebenläufigkeit und Parallelität
I
I
I
I
I
I
Wichtige Eigenschaften von Haskell
I
I
I
I
I
I
I
I
Deterministischer Parallelismus
Datenparallelismus
GPU-Programmierung
STM
Leichtgewichtige Threads
Bewusster Umgang mit Seiteneffekten
Wenig globaler Zustand
Datenabhängigkeiten oft explizit
Ausdrucksstarkes Typsystem: Wenn das Programm kompiliert
funktioniert es auch!
Kurzer, prägnanter und lesbarer Code
Einfaches Abstrahieren
Haskell ist “World’s finest imperative programming language”
Haskell ist performant
Parallelität und Nebenläufigkeit mit Haskell
68 / 68
Herunterladen