Die Funktion detectCores() aus dem Base R-Paket parallel ist sehr populär, wenn man R-Code von mehreren Prozessorkernen oder Arbeitern gleichzeitig ausführen lassen möchte. Ich habe sie selbst häufig genutzt, auch in Youtube-Videos, und in Workshops unterrichtet.
Leider kann detectCores() einige unerwünschte Effekte nach sich ziehen. Glücklicherweise gibt es eine bessere Alternative:
parallelly::availableCores()
parallelly ist ein R-Paket von Henrik Bengtsson, das nicht zu Base R gehört und somit separat installiert werden muss.
Was kann mit detectCores() schief gehen?
detectCores() kann folgende unerwünschte Ergebnisse liefern:
- einen Fehlwert;
- den Wert 1: das ist problematisch, wenn man durch Subtraktion von 1 einen Kern für das Betriebssystem reservieren möchte;
- zu viele Kerne
- nicht die Anzahl „erlaubter“ Kerne – Details weiter unten
Parallelisierung in R: Dank an Henrik Bengtsson
R-Code parallel auf mehreren Kernen oder Arbeitern auszuführen, ist eng mit dem Namen Henrik Bengtsson verbunden. Dieser Artikel basiert auf einem englischsprachigen Blogeintrag von Henrik:
Please avoid detectCores() in your R Packages
Mehr von ihm:
- Webseiten:
https://jottr.org
https://futureverse.org - Github: https://github.com/HenrikBengtsson
- Twitter-Profil: https://twitter.com/henrikbengtsson
R-Pakete von Henrik Bengtsson (unvollständige Auswahl):
Paket | Beschreibung |
future | einheitliche Schnittstelle zu paralleler und verteilter Ausführung von R-Code |
future.apply | Apply-Funktionen auf Basis des future-Frameworks |
progressr | einheitliche Schnittstelle für Fortschritts-Infos in R-Code; hier ein Video über Fortschrittsbalken sowie ein Blogbeitrag |
matrixStats | Methoden, die auf Zeilen und Spalten von Matrizen anwendbar sind (auch Vektoren) |
parallelly | Erweiterungen des parallel-Pakets |
Für weitere R-Pakete siehe seine Profile, insbesondere github.
1. detectCores() kann einen Fehlwert zurückgeben
Siehe help(„detectCores“, package = „parallel“):
Value: An integer, NA if the answer is unknown
Beispiel:
ncores <- detectCores() workers <- parallel::makeCluster(ncores)
Error in makePSOCKcluster(names = spec, …) :
numeric ‘names’ must be >= 1
Lösung: parallelly::availableCores()
2. detectCores() kann den Wert 1 zurückgeben
Somit scheitert diese übliche Anwendung:
ncores <- detectCores() - 1L # kann 0 werden
Lösungen:
# Minimum = 1 parallelly::availableCores() # Einen Kern frei lassen (fürs Betriebssystem), wenn möglich # Minimum = 1 parallelly::availableCores(omit = 1)
3. detectCores() kann zu viele Kerne zurückgeben
- R hat ein Limit für die Anzahl an Verbindungen (connections), die gleichzeitig geöffnet sein können
- Bei R-Version 4.2.2 liegt das theoretische Limit bei 125 Verbindungen
- Das tatsächliche Limit kann darunter liegen, da Verbindungen eventuell anderweitig genutzt werden
Beispiel für eine Maschine mit 192 Kernen:
cl <- parallel::makeCluster(detectCores())
Error in socketAccept(socket = socket, blocking = TRUE, open = “a+b”) : all connections are in use
cl <- parallelly::makeClusterPSOCK(detectCores())
Error: Cannot create 192 parallel PSOCK nodes. Each node needs one connection, but there are only 124 connections left out of the maximum 128 available on this R installation
Somit funktioniert der bisher bewährte Code nicht mehr auf modernen, leistungsfähigen Maschinen.
Lösung:
parallelly::availableCores(constraints = "connections")
Zudem kann man eine R-Option setzen, um den maximalen Rückgabewert von availableCores() zu steuern:
parallelly.availableCores.system
Auch eine Umgebungsvariable steht dafür zur Verfügung:
R_PARALLELLY_AVAILABLECORES_SYSTEM
Zum Beispiel: R_PARALLELLY_AVAILABLECORES_SYSTEM=120
detectCores() ermittelt nicht die Anzahl „erlaubter“ Kerne
a) Auf einem Personal Computer (PC)
Als Entwickler wissen wir nicht, wie viele Kerne der Anwender einsetzen möchte. Es ist besser, den Anwender entscheiden zu lassen, wie viele Kerne für welchen Zweck arbeiten sollen.
Wenn man mehrere R-Sessions gleichzeitig startet, kann detectCores() zu einem Mehrfachen von 100% Auslastung führen, was schnell sehr ineffizient wird!
b) Auf einer von mehreren Anwendern genutzten Maschine
Wenn man „einfach“ (als Voreinstellung, default) alle Kerne einer Maschine nutzt, verlangsamt man alle Prozesse für alle Nutzer. Der Nutzer, der dies verursachte, mag sich dessen gar nicht bewusst sein. Vielleicht hat er nur unseren Code ausgeführt. Möglicherweise ist die Ursache schwer nachvollziehbar und kostet Nerven und Zeit von Administratoren und anderen Usern.
Lösung:
parallelly::availableCores()
Ein Nutzer oder Systemadministrator kann die voreingestellte Anzahl an CPU-Kernen mit einer Umgebungsvariable steuern:
R_PARALLELLY_AVAILABLECORES_FALLBACK
Beispiel: R_PARALLELLY_AVAILABLECORES_FALLBACK=2
c) Gemeinsam genutzter Compute Cluster mit vielen Maschinen
High Perfomance Computer Clusters (HPC) nutzen Job Schedulers.
Slurm-Beispiel (Slurm = Simple Linux Utility for Resource Management):
sbatch --cpus-per-task=48 --mem=256G run_my_rscript.sh
detectCores() respektiert diese Einstellung von Job Schedulers nicht.
Lösung:
parallelly::availableCores()
- respektiert Umgebungsvariablen von üblichen HPC Job Schedulers
- Beispiele:
- Fujitsu Technical Computing Suite (PJM), Grid Engine (SGE), Load Sharing Facility (LSF), PBS/Torque, Simple Linux Utility for Resource Management (Slurm)
d) R-Code in CGroups in einem Linux Container
CGroups = control groups: Ein Prozess erhält eine bestimmte Anzahl Kerne zugewiesen. Beispiel:
docker run --cpuset-cpus=0-2,8 oder docker run --cpu=3.4
detectCores() gibt die CPUs der Hardware zurück, nicht der cgroup!
Beispiel: 96 Kerne, 8 Kerne pro cgroup: Das bedeutet, dass 96 Arbeiter um die Ressourcen von 8 Kernen kämpfen. Das wird schnell sehr ineffizient.
Das kann auch in der Posit Cloud (früher RStudio Cloud) passieren!
Lösung:
parallelly::availableCores()
Henrik Bengtssons Empfehlung
- Der sicherste Weg ist, R-Code und insbesondere R-Pakete so zu gestalten, dass sie in der Voreinstellung Code sequentiell ausführen.
- Der Anwender sollte entscheiden, wie viele Arbeiter ggf. parallel eingesetzt werden.
- Zweitbeste Alternative: detectCores() ersetzen durch parallelly::availableCores()
Weitere Details siehe https://parallelly.futureverse.org/
Alles Gute für Eure R-Projekte! Freue mich sehr über Erfahrungsberichte – lasst mir Kommentare da!
Hier geht’s zum früheren Beitrag R-Code parallelisieren mit parallel::clusterApply(). Dort hatte ich noch detectCores() eingesetzt, aber bereits auf parallelly::availableCores() hingewiesen.
Und hier ein Beitrag über Fortschrittsbalken mit progressr und future, R-Paketen von Henrik Bengtsson.