Praktische Informatik 3 (WS 2010/11) - informatik.uni

Werbung
Fahrplan
Praktische Informatik 3: Einführung in die Funktionale
Programmierung
Vorlesung vom 19.01.2011: Effizienzaspekte
I
Teil II: Funktionale Programmierung im Großen
I
Teil III: Funktionale Programmierung im richtigen Leben
I
Effizient Funktional Programmieren
Universität Bremen
I
Fallstudie: Kombinatoren
Wintersemester 2010/11
I
Eine Einführung in Scala
I
Rückblich & Ausblick
I
Zeitbedarf: Endrekursion — while in Haskell
I
Platzbedarf: Speicherlecks
I
“Unendliche” Datenstrukturen
I
Verschiedene andere Performancefallen:
2 [33]
1 [33]
Inhalt
I
Teil I: Funktionale Programmierung im Kleinen
Christoph Lüth & Dennis Walter
Rev. –revision–
I
I
Effizienzaspekte
I
Überladene Funktionen, Listen
Erste Lösung: bessere Algorithmen
I
Zweite Lösung: Büchereien nutzen
I
Analyse der Auswertungsstrategie
I
. . . und des Speichermanagement
I
Der ewige Konflikt: Geschwindigkeit vs. Platz
I
Effizenzverbesserungen durch
“Usual Disclaimers Apply”:
I
Zur Verbesserung der Effizienz:
I
I
Endrekursion: Iteration in funktionalen Sprachen
I
Striktheit: Speicherlecks vermeiden (bei verzögerter Auswertung)
Vorteil: Effizienz muss nicht im Vordergrund stehen
3 [33]
Endrekursion
4 [33]
Beispiel: Fakultät
I
Definition (Endrekursion)
Eine Funktion ist endrekursiv, wenn
fac1 nicht endrekursiv:
f a c 1 :: I n t e g e r → I n t e g e r
f a c 1 n = i f n == 0 then 1 e l s e n ∗ f a c 1 ( n−1)
(i) es genau einen rekursiven Aufruf gibt,
(ii) der nicht innerhalb eines geschachtelten Ausdrucks steht.
I
I
D.h. darüber nur Fallunterscheidungen: case oder if
I
Entspricht goto oder while in imperativen Sprachen.
I
Wird in Sprung oder Schleife übersetzt.
I
Braucht keinen Platz auf dem Stack.
fac2 endrekursiv:
f a c 2 :: I n t e g e r → I n t e g e r
fac2 n
= f a c ’ n 1 where
f a c ’ :: I n t e g e r → I n t e g e r → I n t e g e r
f a c ’ n a c c = i f n == 0 then a c c
e l s e f a c ’ ( n−1) ( n∗ a c c )
I
fac1 verbraucht Stack, fac2 nicht.
5 [33]
Beispiel: Listen umdrehen
I
6 [33]
Überführung in Endrekursion
Liste umdrehen, nicht endrekursiv:
I
r e v ’ :: [ a ] → [ a ]
rev ’ [ ]
= []
r e v ’ ( x : x s ) = r e v ’ x s ++ [ x ]
I
I
I
Gegeben Funktion
f0 :S →T
f 0 x = if B x then H x
else φ (f 0 (K x )) (E x )
I
Hängt auch noch hinten an — O(n2 )!
Mit K : S → S, φ : T → T → T , E : S → T , H : S → T .
Liste umdrehen, endrekursiv und O(n):
I
Voraussetzung: φ assoziativ, e : T neutrales Element
r e v :: [ a ] → [ a ]
r e v x s = r e v 0 x s [ ] where
rev0 [ ]
ys = ys
rev0 ( x : xs ) ys = rev0 xs ( x : ys )
I
Dann ist endrekursive Form:
f :S→T
f x = g x e where
g x y = if B x then φ (H x ) y
else g (K x ) (φ (E x ) y )
Beispiel: last (rev [1..10000])
7 [33]
8 [33]
Beispiel
I
Beispiel
Länge einer Liste (nicht-endrekursiv)
I
l e n g t h ’ :: [ a ] → I n t
l e n g t h ’ x s = i f n u l l x s then 0
e l s e 1+ l e n g t h ’ ( t a i l x s )
I
I
Zuordnung der Variablen:
K (x ) 7→ tail x
E (x ) 7→ 1
φ(x , y ) 7→ x + y
Es gilt: φ(x , e) = x + 0 = x
B(x ) →
7
H(x ) →
7
e →
7
Damit endrekursive Variante:
l e n g t h :: [ a ] → I n t
l e n g t h x s = l e n x s 0 where
l e n x s y = i f n u l l x s then y −− was: y+ 0
e l s e l e n ( t a i l x s ) ( 1+ y )
null x
0
0
I
(0 neutrales Element)
Allgemeines Muster:
I
Monoid (φ, e): φ assoziativ, e neutrales Element.
I
Zusätzlicher Parameter akkumuliert Resultat.
10 [33]
9 [33]
Endrekursive Aktionen
I
Fortgeschrittene Endrekursion
Nicht endrekursiv:
I
g e t L i n e s ’ :: IO S t r i n g
g e t L i n e s ’ = do s t r ← g e t L i n e
i f n u l l s t r then r e t u r n " "
e l s e do r e s t ← g e t L i n e s ’
r e t u r n ( s t r ++ r e s t )
I
Akkumulation von Ergebniswerten durch closures
I
I
closure: partiell applizierte Funktion
Beispiel: die Klasse Show
I
Nur Methode show wäre zu langsam (O(n2 )):
c l a s s Show a where
show :: a → S t r i n g
Endrekursiv:
g e t L i n e s :: IO S t r i n g
g e t L i n e s = g e t i t " " where
g e t i t r e s = do s t r ← g e t L i n e
i f n u l l s t r then r e t u r n r e s
e l s e g e t i t ( r e s ++ s t r )
I
Deshalb zusätzlich
s h o w s P r e c :: I n t → a → S t r i n g → S t r i n g
show x = s h o w s P r e c 0 x " "
I
String wird erst aufgebaut, wenn er ausgewertet wird (O(n)).
11 [33]
Beispiel: Mengen als Listen
12 [33]
Effizienz durch “unendliche” Datenstrukturen
I
data S e t a = S e t [ a ]
Listen müssen nicht endlich repräsentierbar sein:
I
Beispiel: “unendliche” Liste [2,2,2, . . . ]
I
Liste der natürlichen Zahlen:
I
Syntaktischer Zucker:
Zu langsam wäre
twos = 2 : twos
i n s t a n c e Show a ⇒ Show ( S e t a ) where
show ( S e t e l e m s ) =
" { " ++ i n t e r c a l a t e " , " ( map show e l e m s ) ++ " } "
n a t = n a t s 0 where n a t s n = n : n a t s ( n+ 1 )
Deshalb besser
nat = [0 . . ]
i n s t a n c e Show a ⇒ Show ( S e t a ) where
s h o w s P r e c i ( S e t e l e m s ) = showElems e l e m s where
showElems [ ]
= ( " {} " ++ )
showElems ( x : x s ) = ( ’ { ’ : ) ◦ shows x ◦ s h o w l x s
where s h o w l [ ]
= ( ’} ’ : )
s h o w l ( x : x s ) = ( ’ , ’ : ) ◦ shows x ◦ s h o w l x s
I
Bildung von unendlichen Listen:
c y c l e :: [ a ] → [ a ]
c y c l e x s = x s ++ c y c l e x s
r e p e a t :: a → [ a ]
repeat x = x : repeat x
I
Nützlich für Listen mit unbekannter Länge
I
Obacht: Induktion nur für endliche Listen gültig.
13 [33]
Berechnung der ersten n Primzahlen
I
Eratosthenes — aber bis wo sieben?
I
Lösung: Berechnung aller Primzahlen, davon die ersten n.
Fibonacci-Zahlen
s i e v e :: [ I n t e g e r ] → [ I n t e g e r ]
s i e v e ( p : ps ) =
p : ( s i e v e ( f i l t e r ( \n → n ‘ mod ‘ p /= 0 ) p s ) )
I
14 [33]
I
Aus der Kaninchenzucht.
I
Sollte jeder Informatiker kennen.
f i b :: I n t e g e r → I n t e g e r
fib 0 = 1
fib 1 = 1
f i b n = f i b ( n−1)+ f i b ( n−2)
Keine Rekursionsverankerung ( sieve [ ])
p r i m e s :: [ I n t e g e r ]
primes = s i e v e [2 . . ]
I
Von allen Primzahlen die ersten n:
I
Problem: baumartige Rekursion, exponentieller Aufwand.
f i r s t p r i m e s :: I n t → [ I n t e g e r ]
f i r s t p r i m e s n = take n primes
15 [33]
16 [33]
Fibonacci-Zahlen als Strom
Unendliche Datenstrukturen
I
Lösung: zuvor berechnete Teilergebnisse wiederverwenden.
I
Endliche Repräsentierbarkeit für beliebige Datenstrukturen
I
Sei fibs :: [ Integer ] Strom aller Fibonaccizahlen:
I
E.g. Bäume:
fibs
tail fibs
tail ( tail fibs )
I
1
1
2
1
2
3
2
3
5
3 5 8 13 21 34 55
5 8 13 21 34 55
8 13 21 34 55
data Tree a
= N u l l | Node ( Tree a ) a ( Tree a )
d e r i v i n g Show
twoTree
= Node twoTree 2 twoTree
Damit ergibt sich:
r i g h t S p l i n e n = Node N u l l n ( r i g h t S p l i n e ( n+1 ) )
f i b s :: [ I n t e g e r ]
f i b s = 1 : 1 : z i p W i t h (+) f i b s ( t a i l
fibs )
I
I
n-te Fibonaccizahl mit fibs !! n
I
I
Aufwand: linear, da fibs nur einmal ausgewertet wird.
I
twoTree, twos mit Zeigern darstellbar (e.g. Java, C)
rightSpline 0, nat nicht mit darstellbar
Damit beispielsweise auch Graphen modellierbar
17 [33]
Implementation und Repräsentation von Datenstrukturen
18 [33]
Speicherlecks
I
I
I
Datenstrukturen werden intern durch Objekte in einem Heap
repräsentiert
Garbage collection gibt unbenutzten Speicher wieder frei.
I
I
Bezeichner werden an Referenzen in diesen Heap gebunden
Verzögerte Auswertung effizient, weil nur bei Bedarf ausgewertet wird
I
I
Unendliche Datenstrukturen haben zyklische Verweise
I
I
Kopf wird nur einmal ausgewertet.
c y c l e ( t r a c e " Foo ! " [ 5 ] )
I
Anmerkung: unendlich Datenstrukturen nur sinnvoll für nicht-strikte
Funktionen
Aber Obacht: Speicherlecks!
Eine Funktion hat ein Speicherleck, wenn Speicher unnötig lange im
Zugriff bleibt.
I
I
Unbenutzt: Bezeichner nicht mehr im erreichbar
“Echte” Speicherlecks wie in C/C++ nicht möglich.
Beispiel: getLines, fac2
I
Zwischenergebnisse werden nicht auswertet.
I
Insbesondere ärgerlich bei nicht-terminierenden Funktionen.
19 [33]
Striktheit
I
I
Speicherprofil: fac1 50000, nicht optimiert
Strikte Argumente erlauben Auswertung vor Aufruf
Dadurch konstanter Platz bei Endrekursion.
Erzwungene Striktheit: seq :: α→ β→ β
fac
⊥ ‘seq‘ b = ⊥
a ‘seq‘ b = b
I
I
6,418,898 bytes x seconds
Wed Jan 19 11:24 2011
bytes
I
20 [33]
1,200k
seq vordefiniert (nicht in Haskell definierbar)
($!) :: (a→ b)→ a→ b strikte Funktionsanwendung
TSO
1,000k
800k
f $ ! x = x ‘ seq ‘ f x
BLACKHOLE
I
I
ghc macht Striktheitsanalyse
600k
Fakultät in konstantem Platzaufwand
400k
ARR_WORDS
f a c 3 :: I n t e g e r → I n t e g e r
f a c 3 n = f a c ’ n 1 where
f a c ’ n a c c = s e q a c c $ i f n == 0 then a c c
e l s e f a c ’ ( n−1) ( n∗ a c c )
200k
0k
0.0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0
4.5
seconds
21 [33]
Speicherprofil: fac2 50000, nicht optimiert
Wed Jan 19 11:25 2011
fac
1,481,240 bytes x seconds
bytes
7,213,527 bytes x seconds
Speicherprofil: fac3 50000, nicht optimiert
bytes
fac
22 [33]
1,200k
500k
Wed Jan 19 11:25 2011
450k
TSO
1,000k
TSO
400k
350k
800k
300k
BLACKHOLE
BLACKHOLE
250k
600k
200k
400k
150k
ARR_WORDS
ARR_WORDS
100k
200k
50k
0k
0.0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0
4.5
5.0
0k
0.0
seconds
23 [33]
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0 seconds
24 [33]
Speicherprofil: fac1 50000, optimiert
Wed Jan 19 11:25 2011
fac
226,043 bytes x seconds
Wed Jan 19 11:25 2011
bytes
3,568,845 bytes x seconds
bytes
fac
Speicherprofil: fac2 50000, optimiert
80k
ARR_WORDS
TSO
600k
60k
TSO
integer-gmp:GHC.Integer.Type.S#
400k
40k
MUT_ARR_PTRS_CLEAN
ARR_WORDS
200k
20k
ghc-prim:GHC.Types.:
0k
0.0
0k
0.5
1.0
1.5
2.0
2.5
3.0
3.5
4.0
seconds
0.0
0.5
1.0
1.5
2.0
2.5
3.0
seconds
25 [33]
Speicherprofil: fac3 50000, optimiert
225,010 bytes x seconds
Fazit Speicherprofile
Wed Jan 19 11:25 2011
bytes
fac
26 [33]
80k
I
Geschwindigkeitsgewinn durch Endrekursion nur mit Striktheit
I
Optimierung des ghc meist ausreichend für Striktheitsanalyse, aber
nicht für Endrekursion
ARR_WORDS
60k
TSO
40k
MUT_ARR_PTRS_CLEAN
20k
ghc-prim:GHC.Types.:
0k
0.0
0.5
1.0
1.5
2.0
2.5
3.0
seconds
27 [33]
foldr vs. foldl
I
Wann welches fold?
foldr ist nicht endrekursiv:
I
f o l d r :: ( a → b → b ) → b → [ a ] → b
foldr f z []
= z
f o l d r f z ( x : xs ) = f x ( f o l d r f z xs )
I
28 [33]
I
foldl ist endrekursiv:
I
Strikte Funktionen mit foldl’ falten:
r e v 2 :: [ a ] → [ a ]
rev2 = f o l d l ’ ( f l i p
foldl’ ist strikt und endrekursiv:
foldl
foldl
foldl
let
foldl’ ferner strikt und konstanter Platzaufwand
Wann welches fold?
I
f o l d l :: ( a → b → a ) → a → [ b ] → a
foldl f z []
= z
f o l d l f z ( x : xs ) = f o l d l f ( f z x ) xs
I
foldl endrekursiv, aber traversiert immer die ganze Liste.
I
I
’ :: ( a → b → a ) → a → [ b ] → a
’ f a []
= a
’ f a ( x : xs ) =
a ’ = f a x i n a ’ ‘ seq ‘ f o l d l ’ f a ’ x s
(:))
[]
Wenn nicht die ganze Liste benötigt wird, mit foldr falten:
a l l :: ( a → Bool ) → [ a ] → Bool
a l l p = f o l d r ((&&) ◦ p ) True
I
Potenziell unendliche Listen immer mit foldr falten.
Für Monoid (φ, e) gilt: foldr φ e l = foldl (flip φ) e l
29 [33]
Überladene Funktionen sind langsam.
30 [33]
Listen als Performance-Falle
I
I
Typklassen sind elegant aber langsam.
I
I
I
Listen:
Implementierung von Typklassen: Verzeichnis (dictionary) von
Klassenfunktionen.
I
Überladung wird zur Laufzeit aufgelöst
I
I
I
I
Listen sind keine Felder oder endliche Abbildungen
Bei kritischen Funktionen: Spezialisierung erzwingen durch Angabe der
Signatur
Felder Array ix a (Modul Array aus der Standardbücherei )
I
I
I
NB: Zahlen (numerische Literale) sind in Haskell überladen!
I
I
I
Bsp: facs hat den Typ Num a=> a-> a
I
I
31 [33]
Feste Größe (Untermenge von ix )
Zugriff auf n-tes Element in konstanter Zeit.
Abstrakt: Abbildung Index auf Daten
Endliche Abbildung Map k v (Modul Data.Map)
I
f a c s n = i f n == 0 then 1 e l s e n∗ f a c s ( n−1)
Beliebig lang
Zugriff auf n-tes Element in linearer Zeit.
Abstrakt: frei erzeugter Datentyp aus Kopf und Rest
Beliebige Größe
Zugriff auf n-tes Element in sublinearer Zeit.
Abstrakt: Abbildung Schlüsselbereich k auf Wertebereich v
32 [33]
Zusammenfassung
I
Endrekursion: while für Haskell.
I
Überführung in Endrekursion meist möglich.
I
Noch besser sind strikte Funktionen.
I
Speicherlecks vermeiden: Striktheit und Endrekursion
I
Compileroptimierung nutzen
I
Datenstrukturen müssen nicht endlich repräsentierbar sein
I
Überladene Funktionen sind langsam.
I
Listen sind keine Felder oder endliche Abbildungen.
33 [33]
Herunterladen