Warum Du parallel::detectCores() in R NICHT verwenden solltest

parallel::detectCores() zur Parallelisierung von R-Code ist populär, kann aber Probleme verursachen. Besser: parallelly::availableCores().

parallelly::availableCores() statt parallel::detectCores()

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:

  1. einen Fehlwert;
  2. den Wert 1: das ist problematisch, wenn man durch Subtraktion von 1 einen Kern für das Betriebssystem reservieren möchte;
  3. zu viele Kerne
  4. 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:

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()

parallelly::availableCores() statt parallel::detectCores()
parallelly::availableCores() statt parallel::detectCores()

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.

Freue mich über Kommentare!

Wir benutzen Cookies um die Nutzerfreundlichkeit der Webseite zu verbessen. Durch Deinen Besuch stimmst Du dem zu.