Praktische Informatik 3: Funktionale Programmierung Vorlesung 12 vom 15.01.2013: Effizienzaspekte
Christoph Lüth Universität Bremen Wintersemester 2012/13
Rev. 1980 1 [31]
Fahrplan
I Teil I: Funktionale Programmierung im Kleinen
I Teil II: Funktionale Programmierung im Großen
I Teil III: Funktionale Programmierung im richtigen Leben
IEffizienzaspekte
IEine Einführung in Scala
IRückblich & Ausblick
2 [31]
Inhalt
I Zeitbedarf:Endrekursion—whilein Haskell I Platzbedarf:Speicherlecks
I “Unendliche” Datenstrukturen I Verschiedene anderePerformancefallen:
I ÜberladeneFunktionen,Listen I “Usual Disclaimers Apply”:
I Erste Lösung: bessereAlgorithmen
I Zweite Lösung:Büchereiennutzen
3 [31]
Effizienzaspekte
I ZurVerbesserungder Effizienz:
IAnalyse derAuswertungsstrategie
I. . . und desSpeichermanagement
I Der ewige Konflikt:Geschwindigkeitvs.Platz I Effizenzverbesserungen durch
IEndrekursion: Iteration in funktionalen Sprachen
IStriktheit:Speicherlecksvermeiden (bei verzögerter Auswertung)
I Vorteil: Effizienzmuss nichtim Vordergrund stehen
4 [31]
Endrekursion
Definition (Endrekursion) Eine Funktion istendrekursiv, wenn
(i) es genaueinenrekursiven Aufruf gibt,
(ii) dernichtinnerhalb einesgeschachtelten Ausdruckssteht.
I D.h. darübernur Fallunterscheidungen:caseoderif
I Entsprichtgotooderwhilein imperativen Sprachen.
I Wird inSprungoderSchleifeübersetzt.
I Brauchtkeinen Platzauf dem Stack.
5 [31]
Einfaches Beispiel
I In Haskell:
e v e n x = i f x< 1 then x == 0 e l s e e v e n ( x−2) I Übersetzt nach C:
i n t e v e n (i n t x )
{ i f ( x<1 ) r e t u r n x == 0 ; e l s e r e t u r n ( e v e n ( x−2 ) ) ; } I Äquivalente Formulierung:
i n t e v e n (i n t x )
{ i f ( x< 1 ) r e t u r n x == 0 ;
e l s e { x −= 2 ; r e t u r n e v e n ( x ) ; } } I Iterative Variante mit Schleife:
i n t e v e n (i n t x )
{ w h i l e ( ! ( x<1 ) ) x −= 2 ; r e t u r n x == 0 ; }
6 [31]
Beispiel: Fakultät
I fac1nichtendrekursiv:
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 fac2endrekursiv:
f a c 2 :: I n t e g e r→ I n t e g e r
f a c 2 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 fac1verbraucht Stack,fac2nicht.
7 [31]
Beispiel: Listen umdrehen
I Liste umdrehen,nichtendrekursiv:
r e v ’ :: [ a ]→ [ a ]
r e v ’ [ ] = [ ]
r e v ’ ( x : x s ) = r e v ’ x s ++ [ x ]
IHängt auch nochhintenan —O(n2)!
I Liste umdrehen,endrekursivundO(n):
r e v :: [ a ]→ [ a ]
r e v x s = r e v 0 x s [ ] where r e v 0 [ ] y s = y s
r e v 0 ( x : x s ) y s = r e v 0 x s ( x : y s ) I Beispiel:last (rev [1..10000])
8 [31]
Überführung in Endrekursion
I Gegeben Funktion f0:S→T
f0x = ifB x thenH x
elseφ(f0(K x)) (E x)
I MitK:S→S,φ:T→T→T,E:S→T,H:S→T.
I Voraussetzung:φassoziativ,e:T neutrales Element
I Dann istendrekursiveForm:
f :S→T
f x = g x ewhere
g x y = ifB x thenφ(H x)y elseg(K x) (φ(E x)y)
9 [31]
Beispiel
I Länge einer Liste (nicht-endrekursiv)
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 Zuordnung der Variablen:
K(x) 7→ tailx E(x) 7→ 1 φ(x,y) 7→ x+y
B(x) 7→ nullx H(x) 7→ 0
e 7→ 0 I Es gilt:φ(x,e) =x+0=x (0 neutrales Element)
10 [31]
Beispiel
I DamitendrekursiveVariante:
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 )
I AllgemeinesMuster:
I Monoid(φ,e):φassoziativ,eneutrales Element.
I Zusätzlicher ParameterakkumuliertResultat.
11 [31]
Endrekursive Aktionen
I Nicht endrekursiv:
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 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 )
12 [31]
Fortgeschrittene Endrekursion
I Akkumulationvon Ergebniswerten durchclosures
I closure: partiell applizierte Funktion I Beispiel: die KlasseShow
I Nur Methodeshowwäre zu langsam (O(n2)):
c l a s s Show a where show :: a→ S t r i n g
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)).
13 [31]
Beispiel: Mengen als Listen
data S e t a = S e t [ a ] Zulangsamwäre
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 ) ++ " } "
Deshalbbesser
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 ) = ( ’ { ’ : ) ◦s h o w s x◦s h o w l x s where s h o w l [ ] = ( ’ } ’ : )
s h o w l ( x : x s ) = ( ’ , ’ : ) ◦s h o w s x◦s h o w l x s
14 [31]
Speicherlecks
I Garbage collectiongibtunbenutztenSpeicher wieder frei.
I Unbenutzt: Bezeichner nicht mehr imerreichbar
I Verzögerte Auswertungeffizient, weil nur beiBedarfausgewertet wird
I Aber Achtung:Speicherlecks!
I Eine Funktion hat einSpeicherleck, wenn Speicherunnötiglange im Zugriff bleibt.
I “Echte” Speicherlecks wie in C/C++nicht möglich.
I Beispiel:getLines,fac2
I Zwischenergebnisse werdennicht auswertet.
I Insbesondere ärgerlich beinicht-terminierenden Funktionen.
15 [31]
Striktheit
I Strikte Argumenteerlauben AuswertungvorAufruf
IDadurchkonstanterPlatz beiEndrekursion.
I ErzwungeneStriktheit:seq :: α→β→β
⊥‘seq‘ b = ⊥
a ‘seq‘ b = b
I seqvordefiniert (nichtinHaskell definierbar)
I ($!) :: (a→b)→a→bstrikte Funktionsanwendung f $ ! x =x ‘ s e q ‘ f x
Ighc machtStriktheitsanalyse I Fakultät in konstantem Platzaufwand
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 )
16 [31]
Speicherprofil: fac1 50000, nicht optimiert
fac 1 50000 +RTS -hc (null) 228,418 bytes x seconds Tue Jan 15 13:28 2013
seconds
0.0 0.2 0.4 0.6 0.8
bytes
0k 50k 100k 150k 200k 250k 300k
(51)PINNED (99)fac1/main/Main.CAF
17 [31]
Speicherprofil: fac2 50000, nicht optimiert
fac 2 50000 +RTS -hc (null) 253,330 bytes x seconds Tue Jan 15 13:28 2013
seconds
0.0 0.2 0.4 0.6 0.8 1.0
bytes
0k 50k 100k 150k 200k 250k 300k
(51)PINNED (99)fac2/main/Main.CAF
18 [31]
Speicherprofil: fac3 50000, nicht optimiert
fac 3 50000 +RTS -hc (null) 85,428 bytes x seconds Tue Jan 15 13:29 2013
seconds
0.0 0.2 0.4 0.6 0.8
bytes
0k 20k 40k 60k 80k 100k
(93)GHC.IO.Encoding.CAF (51)PINNED (99)fac3/main/Main.CAF
19 [31]
Speicherprofil: fac1 50000, optimiert
fac 1 50000 +RTS -hc (null) 179,641 bytes x seconds Tue Jan 15 13:29 2013
seconds
0.0 0.2 0.4 0.6 0.8
bytes
0k 50k 100k 150k 200k 250k
(50)PINNED (98)fac1/main
20 [31]
Speicherprofil: fac2 50000, optimiert
fac 2 50000 +RTS -hc (null) 90,657 bytes x seconds Tue Jan 15 13:29 2013
seconds
0.0 0.2 0.4 0.6 0.8
bytes
0k 20k 40k 60k 80k 100k
(88)GHC.IO.Encoding.CAF (50)PINNED (98)fac2/main
21 [31]
Speicherprofil: fac3 50000, optimiert
fac 3 50000 +RTS -hc (null) 85,464 bytes x seconds Tue Jan 15 13:29 2013
seconds
0.0 0.2 0.4 0.6 0.8
bytes
0k 20k 40k 60k 80k 100k
(88)GHC.IO.Encoding.CAF (50)PINNED (98)fac3/main
22 [31]
Fazit Speicherprofile
I Endrekursionnurbeistrikten Funktionenschneller
I Optimierung desghc
I meistausreichendfürStriktheitsanalyse
I abernichtfür Endrekursion
I Deshalb:
I ManuelleÜberführung in Endrekursionsinnvoll
I Compiler-Optimierungfür Striktheit nutzen
23 [31]
foldr vs. foldl
I foldristnicht endrekursiv:
f o l d r :: ( a → b → b ) → b → [ a ] → b
f o l d r f z [ ] = z
f o l d r f z ( x : x s ) = f x ( f o l d r f z x s ) I foldlistendrekursiv:
f o l d l :: ( a → b → a ) → a → [ b ] → a
f o l d l f z [ ] = z
f o l d l f z ( x : x s ) = f o l d l f ( f z x ) x s I foldl’iststriktundendrekursiv:
f o l d l ’ :: ( a→ b→ a )→ a→ [ b ]→ a
f o l d l ’ f a [ ] = a
f o l d l ’ f a ( x : x s ) =
l e t a ’ = f a x i n a ’ ‘ s e q ‘ f o l d l ’ f a ’ x s I Für Monoid(φ,e)gilt:foldrφe l=foldl(flipφ)e l
24 [31]
Wann welches fold?
I foldlendrekursiv, aber traversiert immer dieganzeListe.
I foldl’fernerstriktundkonstanter Platzaufwand I Wann welchesfold?
I Strikte Funktionenmitfoldl’falten:
r e v 2 :: [ a ]→ [ a ]
r e v 2 = f o l d l ’ ( f l i p ( : ) ) [ ]
I Wennnicht die ganze Listebenötigt wird, mitfoldrfalten:
a l l :: ( a→ B o o l )→ [ a ]→ B o o l a l l p = f o l d r ((&&)◦p ) True
I PotenziellunendlicheListenimmermitfoldrfalten.
25 [31]
Effizienz durch “unendliche” Datenstrukturen
I Listen müssen nichtendlich repräsentierbarsein:
INützlichfür Listen mitunbekannter Länge
IObacht:Induktion nur fürendlicheListen gültig.
I Beispiel: Fibonacci-Zahlen
IAus der Kaninchenzucht.
ISollte jeder Informatiker kennen.
f i b ’ :: I n t→ I n t e g e r f i b ’ 0 =1
f i b ’ 1 =1
f i b ’ n = f i b ’ ( n−1)+ f i b ’ ( n−2)
IProblem:baumartigeRekursion,exponentiellerAufwand.
26 [31]
Fibonacci-Zahlen als Strom
I Lösung: zuvor berechneteTeilergebnisse wiederverwenden.
I Seifibs :: [ Integer ]Strom aller Fibonaccizahlen:
f i b s 1 1 2 3 5 8 13 21 34 55
t a i l f i b s 1 2 3 5 8 13 21 34 55
t a i l ( t a i l f i b s ) 2 3 5 8 13 21 34 55
I Damit ergibt sich:
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 f i b s )
I n-te Fibonaccizahl mitfibs !! n
I Aufwand:linear, dafibs nur einmal ausgewertet wird.
27 [31]
Implementation und Repräsentation von Datenstrukturen
I Datenstrukturen werden intern durchObjektein einemHeap repräsentiert
I Bezeichner werden anReferenzenin diesen Heap gebunden I Unendliche Datenstrukturen haben zyklische Verweise
IKopf wird nureinmalausgewertet.
c y c l e ( t r a c e " Foo ! " [ 5 ] )
I Anmerkung: unendlich Datenstrukturen nur sinnvoll fürnicht-strikte Funktionen
28 [31]
Überladene Funktionen sind langsam.
I Typklassen sind elegant aberlangsam.
I Implementierung von Typklassen:Verzeichnis(dictionary) von Klassenfunktionen.
I Überladung wird zurLaufzeitaufgelöst
I Bei kritischen Funktionen:Spezialisierung erzwingendurch Angabe der Signatur
I NB:Zahlen(numerische Literale) sind in Haskellüberladen!
I Bsp:facshat den TypNum a=> a-> a
f a c s n= i f n == 0 then 1 e l s e n∗ f a c s ( n−1)
29 [31]
Listen als Performance-Falle
I Listen sindkeineFelder oder endliche Abbildungen I Listen:
IBeliebiglang
IZugriff aufn-tes Element inlinearerZeit (O(n))
IAbstrakt: frei erzeugter Datentyp aus Kopf und Rest
I FelderArray ix a(ModulArrayaus der Standardbücherei )
IFesteGröße (Untermenge vonix)
IZugriff aufn-tes Element inkonstanterZeit(O(1))
IAbstrakt: Abbildung Index auf Daten
I Endliche AbbildungMap k v(ModulData.Map)
IBeliebige Größe
IZugriff aufn-tes Element insublinearerZeit (O(logn))
IAbstrakt: Abbildung Schlüsselbereichkauf Wertebereichv
ISonderfall:Set k≡Map k Bool
30 [31]
Zusammenfassung
I Endrekursion:whilefür Haskell.
I Überführung in Endrekursionmeist möglich.
I Noch besser sindstrikte Funktionen.
I Speicherlecksvermeiden:StriktheitundEndrekursion I Compileroptimierungnutzen
I Datenstrukturen müssen nichtendlich repräsentierbarsein I Überladene Funktionensind langsam.
I Listensind keine Felder oder endliche Abbildungen.
31 [31]