R Academy · Lección

Paquete parallel y detectCores()

Inicie clústeres fork o de sockets y distribuya el trabajo entre los núcleos de la CPU.

Lección 1 de 413 pasos

Paquete parallel y detectCores() es una lección gratuita de R Academy en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de R Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de R Academy incluye 4 lecciones en total.

¿Por qué usar computación paralela?

Los ordenadores modernos tienen varios núcleos de CPU. De forma predeterminada, R se ejecuta en un solo núcleo y deja los demás inactivos. El paquete parallel (incluido en R) permite aprovechar todos los núcleos para acelerar los cálculos repetitivos.

# 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 y stopCluster

makeCluster(n) inicia n procesos de trabajo. Llame siempre a stopCluster(cl) al terminar para liberar recursos. Una convención habitual consiste en usar detectCores() - 1 para dejar un núcleo disponible para el 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: compartir variables

Los procesos de trabajo tienen su propio espacio de memoria y no pueden ver su entorno global. Use clusterExport(cl, varlist) para copiar los objetos especificados del proceso principal a todos los procesos de trabajo.

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: ejecutar código de configuración

clusterEvalQ(cl, expr) evalúa una expresión en cada proceso de trabajo, lo que resulta útil para cargar paquetes o ejecutar archivos auxiliares en todos los nodos antes de iniciar el cálculo principal.

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 en paralelo

parLapply(cl, X, FUN) es el equivalente paralelo de lapply(). Distribuye los elementos de X entre los procesos de trabajo y recopila los resultados en una lista. Funciona en todas las plataformas (Windows, macOS y 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: resultados simplificados

parSapply(cl, X, FUN) es la versión paralela de sapply(). Simplifica automáticamente el resultado a un vector o una matriz cuando es posible, lo que resulta práctico cuando la función devuelve un escalar.

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: paralelismo basado en bifurcación

mclapply() utiliza forking: copia el proceso de R actual en lugar de iniciar procesos nuevos, por lo que comienza más rápido y proporciona a los procesos de trabajo acceso implícito al entorno principal. Sin embargo, solo funciona en Unix/macOS, no en 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')
}

Comparación de rendimiento: secuencial frente a paralelo

El paralelismo tiene costes adicionales: el inicio del clúster, la serialización de datos y la comunicación entre procesos requieren tiempo. Solo resulta ventajoso cuando el cálculo de cada elemento es lo bastante costoso como para compensar esos costes.

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)

Inicialización del generador aleatorio en paralelo

La generación de números aleatorios en paralelo es compleja: cada proceso de trabajo necesita una secuencia independiente y reproducible. Use clusterSetRNGStream(cl, seed) con el generador L'Ecuyer-CMRG para obtener aleatoriedad paralela reproducible.

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)

Gestión de errores en clústeres

Si un proceso de trabajo genera un error, parLapply() lo vuelve a lanzar en el proceso principal. Envuelva las llamadas en tryCatch() dentro de la función o use tryCatch() alrededor de toda la llamada a parLapply() para gestionar los fallos correctamente.

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)

Patrón práctico de paralelismo

Este es un flujo de trabajo paralelo completo e idiomático: detectar los núcleos, crear el clúster, exportar las dependencias, ejecutar el cálculo, recopilar los resultados y detener siempre el clúster, incluso si se produce un error, mediante 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')

Comprobación rápida

¿Qué función debe utilizar para garantizar que se llame a stopCluster(cl) aunque se produzca un error dentro del código paralelo?

Repaso: paquete parallel

Conceptos clave:

  • detectCores() informa de los núcleos disponibles; use detectCores() - 1 para determinar el tamaño del clúster
  • makeCluster(n) / stopCluster(cl) gestionan el ciclo de vida de los procesos de trabajo
  • clusterExport() copia objetos; clusterEvalQ() ejecuta código de configuración en los procesos de trabajo
  • parLapply() / parSapply() son iteradores paralelos multiplataforma
  • mclapply() solo funciona en Unix, pero inicia más rápido gracias a la bifurcación
  • Use on.exit(stopCluster(cl)) para garantizar la limpieza
  • El paralelismo solo resulta rentable en tareas con un coste computacional elevado
# 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 para empezar

Aprende R con un tutor de IA — gratis

Escribe y ejecuta código real en tu navegador, obtén ayuda instantánea de un tutor de IA disponible 24/7 y continúa donde lo dejaste en la web o en la aplicación.

Cursos
43
Lecciones
159

Preguntas frecuentes

¿La lección «Paquete parallel y detectCores()» es gratis?

Sí — el texto completo de «Paquete parallel y detectCores()» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de R Academy, actualiza a CoddyKit PRO. El curso de R Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Paquete parallel y detectCores()»?

Inicie clústeres fork o de sockets y distribuya el trabajo entre los núcleos de la CPU. Practicas R Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar R Academy?

No se requiere experiencia previa. R Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.

¿Cuánto tiempo toma la lección «Paquete parallel y detectCores()»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de R Academy?

Sí. Cada lección de R Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Paquete parallel y detectCores()
  2. El framework future
  3. furrr: operaciones paralelas de purrr
  4. Depuración y equilibrio de carga del código paralelo
← Volver a R Academy