Warum Code optimieren?
Wer kennt es nicht: Der Code ist geschrieben und nach ein paar Versuchen stellt man fest “Das könnte schneller sein.” Alternativ kann man beim Testen feststellen, dass der Code zu viel Speicherplatz benötigt. Doch welche Zeile ist der Übeltäter? Gibt es überhaupt die eine Funktion, welche den Code verlangsamt? Wir wollen heute genau diesen Fragen nachgehen.
Hierzu benötigen wir nur das Paket profvis. Das Interessante an profvis sind die Bedienerfreundlichkeit und die Visualisierungen des Code-Ablaufs.
Generelles Konzept
Das Grundkonzept von Profiling ist einfach:
Lass den Code laufen
Stop den Code alle paar Millisekunden.
Merke dir was gerade ausgeführt wird
Weiter mit 1. und dem nächsten Code-Abschnitt
R wird bereits mit der Funktion Rprof() ausgeliefert (Paket utils), aber die Funktion ist schwer zu handhaben. Stattdessen können wir das Paket profvis von CRAN herunterladen. profvis kann dann entweder direkt vom RStudio aus gestartet werden (unter "Profile“) oder als Funktion aufgerufen werden.
Beispiel: Simulation zweier Würfel
Als Beispiel nutzen wir folgenden Code, der das Würfeln mit zwei Würfeln simuliert.
Dabei nutzen wir als erste Variante die Simulation auf Basis von Dataframes:
## Anzahl der Würfe
size<-1e6
## Simulation zweier Würfel
ErgebnisseWuerfel<-data.frame(Wuerfel1 = sample(x = 1:6, size= size, replace= TRUE),
Wuerfel2 = sample(x = 1:6, size= size, replace= TRUE) )
## Aufaddieren der Augen
AugenZweierWuerfel<-apply(X = ErgebnisseWuerfel, MARGIN = 1, FUN = sum)
## Plot
barplot(table(AugenZweierWuerfel))
Als zweite Variante nutzen wir Matrix-Operationen:
## Energie Mr. Spock!
ErgebnisseWuerfel2 <-matrix(sample(1:6, size= 2*size, replace= TRUE), ncol= 2)
## Aufaddieren der Augen
AugenZweierWuerfel2 <-rowSums(ErgebnisseWuerfel2)
## Plot
barplot(table(AugenZweierWuerfel2))
Profilen mittels profvis
Nun sehen wir uns an, ob die Dataframe- oder Matrix-Variante besser performen. Dazu haben wir die jeweils vergleichbaren Befehle untereinander geschrieben. Alle Befehle, die wir vergleichen wollen, lassen wir innerhalb von profvis ausführen:
size<-1e6
profvis::profvis({
## Simulation zweier Würfel
# DataFrame
ErgebnisseWuerfel<-data.frame(Wuerfel1 = sample(x = 1:6, size= size, replace= TRUE),
Wuerfel2 = sample(x = 1:6, size= size, replace= TRUE) )
# Matrix
ErgebnisseWuerfel2 <-matrix(sample(1:6, size= 2*size, replace= TRUE), ncol= 2)
## Aufaddieren der Augen
# DataFrame
AugenZweierWuerfel<-apply(X = ErgebnisseWuerfel, MARGIN = 1, FUN = sum)
# Matrix
AugenZweierWuerfel2 <-rowSums(ErgebnisseWuerfel2)
## Histogram erstellen
# DataFrame
barplot(table(AugenZweierWuerfel))
# Matrix
barplot(table(AugenZweierWuerfel2))
})
Die Zeilensummen-Bildung ist deutlich schneller bei der Nutzung von Matrizen als bei Dataframes. Dafür scheint das Plotten des Histogramms bei Verwendung von DataFrames schneller zu sein. Diese Informationen finden sich sowohl im FlameGraph, wie auch im Reiter Data. Im FlameGraph können wir sehen, welche nicht anonymen Funktionen die einzelnen Befehle auslösen. Der Apply-Befehl scheint eine ganze Reihe von Aufrufen zu machen, genau wie das Plotten der Matrix.
Profilen im RStudio
Wie bereits weiter oben erwähnt kann profvis auch direkt über die Registerkarte „Profile“ im RStudio geladen werden.
Dazu kann der interessierende Code-Abschnitt markiert und mittels „Profile selected line(s)“ untersucht werden. Alternativ kann die Funktion auch über die Tasten-Kombination Strg+Shift+Alt+P gestartet werden.
Fazit
profvis bietet umfassende Informationen zum Vergleich oder zur Evaluierung von Funktionen. Mit Hilfe eines FlameGraphs können schnell Programm-Bestandteile ermittelt werden, die viel Speicherbedarf und/ oder lange Laufzeiten haben. Ein Blick in Data gibt zusätzlich Hinweise darauf, was genau die Ressourcen verbraucht. Der Einsatz von profvis hilft dabei performante Programme zu schreiben. Zudem kann es das Verständnis der Abläufe innerhalb von Funktionen fördern.
Comments