R Academy · Lezione

Pacchetto parallel e detectCores()

Avvii cluster fork o socket e distribuisca il lavoro tra i core della CPU

Lezione 1 di 413 passaggi

Pacchetto parallel e detectCores() è una lezione R Academy gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento R Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso R Academy include 4 lezioni in totale.

Perché il calcolo parallelo?

I computer moderni dispongono di più core CPU. Per impostazione predefinita, R viene eseguito su un solo core, lasciando inattivi gli altri. Il pacchetto parallel (integrato in R) consente di sfruttare tutti i core per velocizzare i calcoli ripetitivi.

# Check how many cores your machine has
library(parallel)

total_cores <- detectCores()
logical_cores <- detectCores(logical = TRUE)
physical_cores <- detectCores(logical = FALSE)

cat('Total logical cores:', total_cores, '
')
cat('Physical cores:', physical_cores, '
')

makeCluster e stopCluster

makeCluster(n) avvia n processi worker. Chiami sempre stopCluster(cl) al termine per liberare le risorse. Una convenzione comune consiste nell'utilizzare detectCores() - 1 per lasciare un core al sistema operativo.

library(parallel)

# Spawn workers (leave 1 core for system)
n_cores <- detectCores() - 1
cl <- makeCluster(n_cores)

cat('Cluster created with', n_cores, 'workers\n')

# Always clean up!
stopCluster(cl)
cat('Cluster stopped.\n')

clusterExport: condividere le variabili

I processi worker hanno uno spazio di memoria proprio e non possono vedere l'ambiente globale. Utilizzi clusterExport(cl, varlist) per copiare gli oggetti denominati dal master a tutti i worker.

library(parallel)
cl <- makeCluster(2)

# Define a variable and a function in the master
base_value <- 100
add_base <- function(x) x + base_value

# Export them to workers
clusterExport(cl, varlist = c('base_value', 'add_base'))

# Now workers can use them
result <- parLapply(cl, 1:4, function(x) add_base(x))
cat(unlist(result), '\n')  # 101 102 103 104

stopCluster(cl)

clusterEvalQ: eseguire il codice di configurazione

clusterEvalQ(cl, expr) valuta un'espressione su ogni worker: è utile per caricare pacchetti o includere file di supporto su tutti i nodi prima dell'inizio del calcolo principale.

library(parallel)
cl <- makeCluster(2)

# Load a package on every worker
clusterEvalQ(cl, {
  library(stats)
  set.seed(42)  # set seed per worker
})

# Each worker can now use stats functions
result <- parLapply(cl, 1:4, function(n) rnorm(n, mean = 0, sd = 1))
result[[1]]  # one random normal value

stopCluster(cl)

parLapply: lapply parallelo

parLapply(cl, X, FUN) è l'equivalente parallelo di lapply(). Distribuisce gli elementi di X tra i worker e raccoglie i risultati in una lista. Funziona su tutte le piattaforme (Windows, macOS, Linux).

library(parallel)
cl <- makeCluster(2)

# Simulate slow computation: sleep 0.1s per item
clusterEvalQ(cl, Sys.sleep)

slow_square <- function(x) {
  Sys.sleep(0.05)
  x^2
}

clusterExport(cl, 'slow_square')

system.time(
  result <- parLapply(cl, 1:8, slow_square)
)
cat(unlist(result), '\n')  # 1 4 9 16 25 36 49 64

stopCluster(cl)

parSapply: risultati semplificati

parSapply(cl, X, FUN) è la versione parallela di sapply(). Semplifica automaticamente il risultato trasformandolo in un vettore o una matrice quando possibile, risultando comoda quando la funzione restituisce uno scalare.

library(parallel)
cl <- makeCluster(2)

# parSapply returns a simplified vector
squares <- parSapply(cl, 1:10, function(x) x^2)
cat(squares, '\n')  # 1 4 9 16 25 36 49 64 81 100

# Returns a matrix if FUN returns a vector of same length
stats_result <- parSapply(cl, 1:4, function(n) {
  x <- rnorm(100)
  c(mean = mean(x), sd = sd(x))
})
print(stats_result)  # 2x4 matrix

stopCluster(cl)

mclapply: parallelismo basato sul fork

mclapply() utilizza il forking: copia il processo R corrente invece di crearne di nuovi, risultando più veloce all'avvio e dando ai worker accesso implicito all'ambiente del processo padre. Tuttavia, funziona solo su Unix/macOS, non su Windows.

library(parallel)

# mclapply: Unix/macOS only
# Workers inherit the parent environment automatically
base_value <- 42

if (.Platform$OS.type != 'windows') {
  result <- mclapply(
    1:8,
    function(x) x * base_value,  # base_value visible without export
    mc.cores = 4
  )
  cat(unlist(result), '\n')
} else {
  cat('mclapply not supported on Windows. Use parLapply instead.\n')
}

Benchmark sequenziale e parallelo

Il parallelismo comporta un overhead: l'avvio del cluster, la serializzazione dei dati e la comunicazione tra processi richiedono tutti tempo. Conviene solo quando il calcolo per elemento è abbastanza costoso da compensare tale overhead.

library(parallel)
cl <- makeCluster(2)

# Task: compute 100 iterations of matrix multiply
heavy_task <- function(n) {
  m <- matrix(rnorm(200), nrow = 100)
  sum(m %*% t(m))
}
clusterExport(cl, 'heavy_task')

seq_time <- system.time(lapply(1:20, heavy_task))[['elapsed']]
par_time <- system.time(parLapply(cl, 1:20, heavy_task))[['elapsed']]

cat('Sequential:', round(seq_time, 3), 's\n')
cat('Parallel:  ', round(par_time, 3), 's\n')
cat('Speedup:   ', round(seq_time / par_time, 2), 'x\n')

stopCluster(cl)

Inizializzare l'RNG in parallelo

La generazione di numeri casuali in parallelo è complessa: ogni worker ha bisogno di un flusso indipendente e riproducibile. Utilizzi clusterSetRNGStream(cl, seed) con il generatore L'Ecuyer-CMRG per ottenere casualità parallela riproducibile.

library(parallel)
cl <- makeCluster(2)

# Set reproducible RNG streams across workers
clusterSetRNGStream(cl, iseed = 123)

# Each worker uses its own independent random stream
results1 <- parSapply(cl, 1:6, function(i) rnorm(1))

# Reset and repeat — same results
clusterSetRNGStream(cl, iseed = 123)
results2 <- parSapply(cl, 1:6, function(i) rnorm(1))

cat('Run 1:', round(results1, 4), '\n')
cat('Run 2:', round(results2, 4), '\n')
cat('Identical:', identical(results1, results2), '\n')

stopCluster(cl)

Gestione degli errori nei cluster

Se un worker genera un errore, parLapply() lo rilancia nel processo master. Racchiuda le chiamate in tryCatch() all'interno della funzione oppure utilizzi tryCatch() intorno all'intera chiamata a parLapply() per gestire gli errori in modo corretto.

library(parallel)
cl <- makeCluster(2)

# Wrap risky code inside the worker function
safe_log <- function(x) {
  tryCatch(
    log(x),
    warning = function(w) NA_real_,
    error   = function(e) NA_real_
  )
}
clusterExport(cl, 'safe_log')

# -1 produces NaN warning, 'a' produces an error
input <- list(4, 9, -1, 'a', 16)
result <- parLapply(cl, input, safe_log)
cat(unlist(result), '\n')  # 1.386 2.197 NaN NA 2.773

stopCluster(cl)

Pattern pratico per il calcolo parallelo

Ecco un flusso di lavoro parallelo completo e idiomatico: rilevare i core, creare il cluster, esportare le dipendenze, eseguire il calcolo, raccogliere i risultati e arrestare sempre il cluster, anche in caso di errore, utilizzando on.exit().

library(parallel)

run_parallel <- function(data, fn, n_workers = detectCores() - 1) {
  cl <- makeCluster(n_workers)
  on.exit(stopCluster(cl))  # guaranteed cleanup

  clusterExport(cl, 'fn', envir = environment())

  result <- parLapply(cl, data, fn)
  result
}

# Example: bootstrap mean estimation
samples <- lapply(1:100, function(i) rnorm(50, mean = 5, sd = 2))
means <- run_parallel(samples, mean)

cat('Grand mean:', round(mean(unlist(means)), 3), '\n')
cat('95% CI: [',
    round(quantile(unlist(means), 0.025), 3), ',',
    round(quantile(unlist(means), 0.975), 3), ']\n')

Verifica rapida

Quale funzione dovrebbe utilizzare per garantire che stopCluster(cl) venga chiamata anche se si verifica un errore nel codice parallelo?

Riepilogo: pacchetto parallel

Punti chiave:

  • detectCores() restituisce il numero di core disponibili; utilizzi detectCores() - 1 per la dimensione del cluster
  • makeCluster(n) / stopCluster(cl) gestiscono il ciclo di vita dei worker
  • clusterExport() copia gli oggetti; clusterEvalQ() esegue il codice di configurazione sui worker
  • parLapply() / parSapply() sono iteratori paralleli multipiattaforma
  • mclapply() funziona solo su Unix, ma è più veloce all'avvio grazie al forking
  • Utilizzi on.exit(stopCluster(cl)) per garantire la pulizia delle risorse
  • Il parallelismo conviene solo per attività con un carico computazionale elevato
# Minimal reproducible parallel pattern
library(parallel)

cl <- makeCluster(max(1, detectCores() - 1))
on.exit(stopCluster(cl))

clusterSetRNGStream(cl, iseed = 42)
result <- parSapply(cl, 1:8, function(x) x^2 + rnorm(1, 0, 0.1))
cat(round(result, 2), '\n')
Gratis per iniziare

Impara R con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
43
Lezioni
159

Domande Frequenti

La lezione «Pacchetto parallel e detectCores()» è gratuita?

Sì — il testo completo di «Pacchetto parallel e detectCores()» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso R Academy, passa a CoddyKit PRO. Il corso R Academy include 4 lezioni in totale.

Cosa imparerò in «Pacchetto parallel e detectCores()»?

Avvii cluster fork o socket e distribuisca il lavoro tra i core della CPU Eserciti R Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare R Academy?

Non è richiesta alcuna esperienza precedente. R Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.

Quanto tempo richiede la lezione «Pacchetto parallel e detectCores()»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione R Academy?

Sì. Ogni lezione R Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Pacchetto parallel e detectCores()
  2. Il framework future
  3. furrr: operazioni parallele di purrr
  4. Debugging e bilanciamento del carico nel codice parallelo
← Torna a R Academy