In einem früheren Beitrag / Video nutzten wir die clusterApply()-Funktion, um R-Code zu parallelisieren. Wie sieht es aus, wenn sich die Laufzeiten der einzelnen Aufgaben deutlich unterscheiden?
Zu Demonstrationszwecken stellen wir eine simple Aufgabe: Sys.sleep, das heißt „Pause machen“. In realen Anwendungen stehen hier dann Berechnungen / Datenoperationen, die unterschiedlich lange dauern.
Vorbereitung der Parallelisierung
library(parallel) library(snow) library(bench) set.seed(2020) tasktime <- runif(30, min = 0, max = 0.1) cl <- makeCluster(detectCores() - 1)
Das snow-Paket wird normalerweise nicht mehr für Parallelisierung benötigt, da die wichtigsten Funktionen in das Base-R-Paket parallel aufgenommen wurden. Hier nutzen wir jedoch eine Funktion daraus zur Visualisierung der Auslastung der Arbeiter.
tasktime enthält 30 Zufallszahlen zwischen 0 und 0,1, die als unterschiedliche Laufzeiten dienen. makeCluster bereitet die Arbeiter vor. Auf Server-Umgebungen sollte detectCores() nicht verwendet werden, um den Server nicht (zu stark) zu blockieren. Eine Alternative ist parallelly::availableWorkers(), das auch Umgebungsvariablen berücksichtigen kann.
Benchmarks: clusterApply vs. clusterApplyLB und lapply
Für die Benchmarks nutzen wir wieder das bench-Paket:
bench::mark( lapply(tasktime, Sys.sleep), clusterApply(cl, tasktime, Sys.sleep), clusterApplyLB(cl, tasktime, Sys.sleep), iterations = 1 )
Die genauen Laufzeiten können je nach Hardware variieren. In meinem Fall läuft lapply() 1,68 Sekunden, clusterApply() rund 300 Millisekunden, und clusterApplyLB() ist fast doppelt so schnell mit 185 Millisekunden. Wie kommt der Geschwindigkeitsgewinn zustande?
lapply() läuft sequentiell – ein Arbeiter war während der Aufgabe permanent ausgelastet.
clusterApply() schafft es nicht, die Arbeiter durchgängig auszulasten – es bleiben Lücken zwischen den grünen Balken, weil schnellere Arbeiter auf langsamere warten müssen.
Load Balancing: Lastenausgleich zwischen den Arbeitern
clusterApplyLB() kann die Arbeiter besser auslasten: Wer mit seiner Aufgabe fertig ist, erhält in aller Regel sofort eine neue. So gelingt hier eine deutlich kürzere Gesamtlaufzeit. Das LB steht für Load Balancing: Lastenausgleich zwischen den Arbeitern.
Fazit: R-Code beschleunigen durch Parallelisierung
clusterApplyLB() ist also ein starkes Werkzeug speziell dann, wenn deutlich unterschiedliche Laufzeiten bei den Aufgaben der einzelnen Arbeiter zu erwarten sind. Insgesamt sind wesentliche Geschwindigkeitsgewinne erreichbar durch Parallellisierung.
Viel Erfolg bei Euren R-Projekten! Welche Erfahrungen habt Ihr mit der Optimierung von R-Skripten gemacht?