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