Stabilirea planurilor de viitor în funcțiile R – și de ce probabil că nu ar trebui să

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

(Acest articol a fost publicat pentru prima dată pe Jottr pe rși a contribuit cu drag la R-Bloggers). (Puteți raporta problema despre conținutul de pe această pagină aici)


Doriți să vă împărtășiți conținutul pe R-Bloggers? Faceți clic aici dacă aveți un blog sau aici dacă nu.

Zidul balonului hexlogo „viitor”

viitor Pachetul sărbătorește zece ani pe CRAN din 19 iunie 2025. Aceasta este a doua dintr -o serie de postări pe blog care evidențiază îmbunătățiri recente la futureverse Ecosistem.

Tl; dr

Acum puteți folosi

my_fcn <- function(...) {
  with(plan(multisession), local = TRUE)
  ...
} 

la temporar Setați un viitor backend pentru utilizare în funcția dvs. Acest lucru garantează că orice modificare este anulată atunci când funcția iese, chiar dacă există o eroare sau o întrerupere.

Dar, chiar recomand nu Făcând orice, așa cum voi încerca să explic mai jos.

Decuplarea intenției de paralelizare și cum să o executați

Filozofia de design de bază a futureverse este:

„Dezvoltatorul decide ce să se paraleleze, utilizatorul decide unde și cum.”

Această decuplare a intenție (ce să se paraleleze) și execuţie (Cum să o faceți) face codul scris folosind Futureverse flexibil, portabil și ușor de întreținut.

Mai exact, dezvoltatorul Controlează ce să se paraleleze folosind
future() sau abstracții de nivel superior precum future_lapply() şi
future_map() pentru a marca regiunile de cod care pot rula concomitent. Codul nu face presupuneri cu privire la mediul de calcul și, prin urmare, este agnostic la care se folosește viitorul backend, de exemplu

y <- future_lapply(X, slow_fcn)

şi

y <- future_map(X, slow_fcn)

Rețineți cum nu există nimic în acele două apeluri funcționale care să specifice modul în care acestea sunt paralelizate, dacă sunt deloc. În schimb, utilizatorul final (de exemplu, analist de date, utilizator HPC sau Runner Script) controlează strategia de execuție Prin setarea viitorului backend prin intermediul plan()de exemplu, multisesiune secvențială, încorporată, încorporată, viitor.callrși
Viitor.Mirai backend -uri. Acest lucru permite utilizatorului să scaleze același cod de la un caiet la un cluster HPC sau un mediu cloud fără a schimba codul original.

Putem găsi acest design de intenția de decuplare și execuție de asemenea, în cadrele tradiționale de paralelizare R. În paralel Pachet pe care îl avem setDefaultCluster()pe care utilizatorul îl poate seta pentru a controla tipul de cluster implicit atunci când niciunul nu este specificat în mod explicit. Pentru ca acest lucru să fie utilizat, dezvoltatorul trebuie să se asigure că utilizează implicit cl = NULLfie în mod explicit, ca în:

y <- parLapply(cl = NULL, X, slow_fcn)

sau implicit, asigurându -vă că toate argumentele sunt numite, ca în:

y <- parLapply(X = X, FUN = slow_fcn)

Din păcate, acest lucru este rar folosit – în schimb parLapply(cl, X, FUN)
este de departe cel mai obișnuit mod de utilizare paralel Pachet, ceea ce duce la un control puțin sau fără niciun control pentru utilizatorul final.

prezice Pachetul a avut un succes mai mare cu această filozofie de proiectare. Acolo dezvoltatorul scrie:

y <- foreach(x = X) %dopar% { slow_fcn(x) }

Fără nicio opțiune în acel apel pentru a specifica ce backend paralel să folosești. În schimb, utilizatorul controlează de obicei backend -ul paralel prin intermediul așa -numitului adaptor Foreach „Dopar”, de exemplu doParallel::registerDoParallel(), doMC::registerDoMC()și
doFuture::registerDoFuture(). Din păcate, există modalități prin care dezvoltatorul să scrie foreach() cu %dopar% declarații astfel încât codul funcționează numai cu un backend paralel specific. Indiferent, este clar din proiectele lor, că ambele pachete au împărtășit aceeași filozofie de proiectare fundamentală a intenția de decuplare și execuție așa cum se folosește în futureverse. Puteți citi mai multe despre acest lucru în introducerea articolului meu H. Bengtsson (2021).

Când scrieți scripturi sau documente Rmarkdown, vă recomand să puneți un cod care să controleze execuția (de exemplu, plan(), registerDoNnn()și
setDefaultCluster()) în vârf, imediat după orice
library() declarații. De asemenea, aici prefer să pun setări globale, cum ar fi options() declarații. Acest lucru face mai ușor pentru oricine să identifice ce setări sunt disponibile și utilizate de script. De asemenea, evită aglomerarea restul codului cu astfel de detalii.

Îndepărtându -se de filozofia de design de bază

Un avantaj practic al proiectării de decuplare de mai sus este faptul că există un singur loc în care paralelizarea este controlată, în loc să fie împrăștiată în întregul cod, de exemplu, ca argumente paralele speciale pentru apeluri funcționale diferite. Acest lucru face mai ușor pentru utilizatorul final, dar și pentru dezvoltatorul de pachete care nu trebuie să se îngrijoreze de cum ar trebui să arate API -urile lor și ce argumente ar trebui să ia.

Acestea fiind spuse, unii dezvoltatori de pachete preferă să expună controlul paralelizării prin argumente funcționale speciale. Dacă căutăm pachete CRAN, găsim argumente precum parallel = FALSE, ncores = 1și
cluster = NULL Acestea sunt apoi utilizate intern pentru a configura backend -ul paralel. Dacă scrieți funcții care adoptă această abordare, este
critic că vă amintiți să setați backend -ul doar temporar, ceea ce se poate face prin intermediul on.exit()de exemplu

my_fcn <- function(xs, ncores = 1) {
  if (ncores > 1) {
    cl <- parallel::makeCluster(ncores)
    on.exit(parallel::stopCluster(cl))
    y <- parLapply(cl = cl, xs, slow_fcn)
  } else {
    y <- lapply(xs, slow_fcn)
  }
  y
}

Dacă utilizați Futureverse, puteți utiliza:

my_fcn <- function(xs, ncores = 1) {
  old_plan <- plan(multisession, workers = ncores)
  on.exit(plan(old_plan))
  y <- future_lapply(xs, slow_fcn)
  y
}

Și, de atunci viitor 1.40.0 (2025-04-10), puteți obține același lucru cu o singură linie de cod:

my_fcn <- function(xs, ncores = 1) {
  with(plan(multisession, workers = ncores), local = TRUE)
  y <- future_lapply(xs, slow_fcn)
  y
}

Sper că această adăugare scade riscul de a uita de a anula orice modificări făcute de plan() Funcții în interior. Dacă uitați, atunci puteți trece peste ceea ce utilizatorul intenționează să utilizeze în altă parte. De exemplu, s -ar putea să fi setat plan(batchtools_slurm) pentru a-și rula codul R într-un cluster Slurm de înaltă performanță (HPC), dar dacă schimbați
plan() În cadrul funcției pachetului dvs. fără a vă dezlega modificările, atunci utilizatorul este pregătit pentru o surpriză și poate și ore de depanare.

Dar, vă rugăm să evitați schimbarea viitoarelor backend -uri dacă puteți

Încă vreau să pledez cu dezvoltatorii de pachete pentru a evita stabilirea viitorului backend, chiar temporar, în funcțiile lor. Există și alte motive pentru a nu face acest lucru. De exemplu, dacă oferiți utilizatorilor un ncores Argumente pentru controlul cantității de paralelizare, riscați să blocați utilizatorul într -un backend paralel specific. Un model comun este de a folosi plan(multisession, workers =
ncores)
ca în exemplele de mai sus. Cu toate acestea, acest lucru împiedică utilizatorul să profite de alte backend -uri paralele strâns legate, de exemplu plan(callr, workers = ncores) şi plan(mirai_multisession,
workers = ncores)
. viitor.callr Backend rulează fiecare sarcină paralelă într -o nouă sesiune R care este închisă imediat după aceea, ceea ce este benefic atunci când memoria este factorul limitativ.
Viitor.Mirai Backend-ul este optimizat pentru a avea o latență scăzută, ceea ce înseamnă că poate paraleliza, de asemenea, sarcini pe termen mai scurt, ceea ce altfel nu ar putea merita paralelizând. De asemenea, contrar multisessionaceste backend-uri alternative pot folosi toate nucleele CPU disponibile pe hardware modern, de exemplu, mașini 192 și 256 de nuclee. multisession backend, care se bazează pe paralel PSOCK Clusters, este limitat la maximum 125 de lucrători paraleli, deoarece fiecare lucrător paralel consumă o conexiune R, iar R poate avea 125 de conexiuni deschise în orice moment. Există modalități de a crește această limită, dar necesită totuși muncă. Vedea parallelly::availableConnections() Pentru mai multe detalii despre această problemă și cum să creșteți numărul maxim de conexiuni.

Desigur, puteți adăuga un alt argument „paralel” pentru a permite utilizatorilor dvs. să controleze și ce viitor backend să utilizeze, de exemplu backend =
multisession
şi ncores = 1. Dar, s -ar putea să nu fie suficient – există backend -uri care iau argumente suplimentare, pe care trebuie să le susțineți și în fiecare dintre funcțiile dvs. În cele din urmă, noi backend -uri vor fi implementate de alții în viitor (Pun intenționat și nu) și nu putem prezice ce vor solicita.

În legătură cu aceasta, lucrez la modalități pentru (i) futureverse pentru a alege între un set de backend -uri paralele – nu doar unul, (ii) pe baza specificațiilor resurselor (de exemplu, nevoile de memorie și timpii de rulare maxime) pentru declarații viitoare specifice. Acest lucru va da înapoi un anumit control dezvoltatorului cu privire la modul în care se întâmplă execuția și mai multe opțiuni pentru ca utilizatorul final să se extindă la diferite tipuri de resurse de calcul. De exemplu, a future_map() Apelați cu o cerință de memorie de 192-GIB poate fi trimisă numai la backend-uri „memorie mare” și, dacă nu este disponibil, aruncați o eroare instantanee. Un alt exemplu este un future_map() Apelați cu o memorie de 256-MIB și o cerință de rulare de 5 minute-care este suficient de mică pentru a fi trimisă către un backend AWS Lambda sau GCS Cloud Funcții, dacă utilizatorul a specificat un astfel de backend.

În rezumat, susțin că este mai bine să lăsați utilizatorul să controleze deplin asupra viitorului backend, lăsându -i să -l seteze prin intermediul plan()de preferință în vârful scenariilor lor. Dacă nu este posibil, vă rugăm să vă asigurați că utilizați with(plan(...), local = TRUE).

Fie ca viitorul să fie cu tine!

Henrik

Referinţă

  • H. Bengtsson, Un cadru de unificare pentru procesarea paralelă și distribuită în R folosind futures, R Journal (2021) 13: 2, paginile 208-227 (Rezumat, PDF)

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.