Apliación del paquete parallel

Descripción del paquete

Usado como soporte para computación paralela, esto puede ser implementado mediante: forking (tomado del paquete multicore), sockets (tomado del paquete snow) y generación de números aleatorios.

  • socket: lanza una nueva versión de R en cada núcleo. Técnicamente esta conexión se realiza a través de una red (por ejemplo, lo mismo que si se conectara a un servidor remoto), pero la conexión se realiza en su propio ordenador.
  • forking: copia toda la versión actual de R y la traslada a un nuevo núcleo.

Además cada uno de ellos tiene sus ventajas y desventajas: 1. El primero funciona en cualquier sistema (incluido Windows), cosa que el segúndo solo en sistemas de tipo POSIX. 2. El segundo es más fácil de implentar, que el socket.

Aplicación del paquete

Librerias utilizadas

library(parallel)

Librerias utilizadas

La función para iniciar un cluster es makeCluster, que toma como argumento el número de núcleos:

numCores <- detectCores()
numCores
## [1] 8

La función toma como argumento un tipo que puede ser PSOCK (la versión socket) o FORK (la versión fork). Generalmente, mclapply debería usarse para el enfoque de bifurcación, así que no hay necesidad de cambiar esto.

Código de preprocesamiento

Cuando se utiliza el enfoque de socket para el procesamiento paralelo, cada proceso se inicia fresco, por lo que cosas como paquetes cargados y cualquier variable existente en su sesión actual no existen. En su lugar, debemos moverlas a cada proceso.

La forma más genérica de hacer esto es la función clusterEvalQ, que toma un cluster y cualquier expresión, y ejecuta la expresión en cada proceso.

cl <- makeCluster(numCores)
clusterEvalQ(cl, library(lme4))
## [[1]]
## [1] "lme4"      "Matrix"    "stats"     "graphics"  "grDevices" "utils"    
## [7] "datasets"  "methods"   "base"     
## 
## [[2]]
## [1] "lme4"      "Matrix"    "stats"     "graphics"  "grDevices" "utils"    
## [7] "datasets"  "methods"   "base"     
## 
## [[3]]
## [1] "lme4"      "Matrix"    "stats"     "graphics"  "grDevices" "utils"    
## [7] "datasets"  "methods"   "base"     
## 
## [[4]]
## [1] "lme4"      "Matrix"    "stats"     "graphics"  "grDevices" "utils"    
## [7] "datasets"  "methods"   "base"     
## 
## [[5]]
## [1] "lme4"      "Matrix"    "stats"     "graphics"  "grDevices" "utils"    
## [7] "datasets"  "methods"   "base"     
## 
## [[6]]
## [1] "lme4"      "Matrix"    "stats"     "graphics"  "grDevices" "utils"    
## [7] "datasets"  "methods"   "base"     
## 
## [[7]]
## [1] "lme4"      "Matrix"    "stats"     "graphics"  "grDevices" "utils"    
## [7] "datasets"  "methods"   "base"     
## 
## [[8]]
## [1] "lme4"      "Matrix"    "stats"     "graphics"  "grDevices" "utils"    
## [7] "datasets"  "methods"   "base"

Uso de par*apply

Existen versiones paralelas de las tres sentencias apply principales: parApply, parLapply y parSapply para apply, lapply y sapply respectivamente. Toman un argumento adicional para el cluster sobre el que operar.

Cronometrar esto es complicado, si sólo cronometramos la llamada a parLapply no estamos capturando el tiempo para abrir y cerrar el cluster, y si cronometramos todo, estamos incluyendo la llamada a lme4. Para ser completamente justos, necesitamos incluir la carga de lme4 en los tres casos.

lapply
library(parallel)
f <- function(i) {
  lmer(Petal.Width ~ . - Species + (1 | Species), data = iris)
}

system.time({
  library(lme4)
  save1 <- lapply(1:100, f)
})
##    user  system elapsed 
##    7.81    0.94   14.99
mclapply
library(parallel)
f <- function(i) {
  lmer(Petal.Width ~ . - Species + (1 | Species), data = iris)
}

system.time({
  library(lme4)
  save2 <- mclapply(1:100, f)
})
##    user  system elapsed 
##    4.36    0.17    5.53
parLapply
library(parallel)
f <- function(i) {
  lmer(Petal.Width ~ . - Species + (1 | Species), data = iris)
}

system.time({
  cl <- makeCluster(detectCores())
  clusterEvalQ(cl, library(lme4))
  save3 <- parLapply(cl, 1:100, f)
  stopCluster(cl)
})
##    user  system elapsed 
##    0.17    0.37   22.62

Cerrar el clúster

Esto no es totalmente necesario, pero es una buena práctica. Si no se detiene, los procesos continúan ejecutándose en segundo plano, consumiendo recursos, y cualquier nuevo proceso puede ser ralentizado o retrasado. Si sale de R, también debería cerrar automáticamente todos los procesos. Esto no elimina el objeto, que en este caso sería el cl, sólo el cluster al que se refiere en segundo plano.

# stopCluster(cl)