Code-Optimierung mit dem RStudioProfiler

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:

  1. Lass den Code laufen

  2. Stop den Code alle paar Millisekunden.

  3. Merke dir was gerade ausgeführt wird

  4. 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))
  
 })


Zusammenfassung im Reiter FlameGraph

Visualisierung im Reiter FlameGraph

Informationen unter Data

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.


Nutzung des Profilers in Rstudio

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.