Cualquier operación donde no todos los pasos dependen de los resultados de los pasos anteriores es un candidato para ser paralelizado.
Un cluster es un grupo de dos o más núcleos que llevan a cabo tareas de forma conjunta en paralelo.
> sapply(1:10, function(x) 2^x)
[1] 2 4 8 16 32 64 128 256 512 1024
El paquete parallel
[2] facilita la
ejecución paralela en R. No requiere instalación manual
ya que forma parte de la instalación base de R. Se carga para
su uso con la
instrucción library(parallel)
que no produce ninguna salida en el caso de que haya sido exitosa su
carga al sistema.
Se puede determinar el número de núcleos disponibles en un sistema con la llamada
> detectCores()
[1] 8
> detectCores(logical=FALSE)
[1] 4
No conviene utilizar todos los núcleos en las tareas de R, ya
que el sistema operativo y su interfaz de usuario suelen requerir
mantenerse responsivos durante los cálculos realizados, por lo
cual es recomendable crear un cluster con un número menor de
núcleos, por ejemplo
cluster <- makeCluster(detectCores() -
1)
. Teniendo el cluster, se puede realizar llamadas que
lo aprevechen, como por ejemplo
> parSapply(cluster, 1:10, function(x) 2^x)
[1] 2 4 8 16 32 64 128 256 512 1024
Una vez que ya no se requiera el cluster, se le libera con la
instrucción stopCluster(cluster)
.
Cuando se ocupa utilizar una variable o una librería dentro de lo que se evalúa en el cluster, es necesario exportarlo de manera explícita al cluster:
> cluster <- makeCluster(detectCores() - 1)
> desde <- 3
> hasta <- 8
> parSapply(cluster, desde:hasta, function(x) 2^x)
[1] 8 16 32 64 128 256
> base <- 3
> parSapply(cluster, desde:hasta, function(x) base^x)
Error in checkForRemoteErrors(val) :
6 nodes produced errors; first error: object 'base' not found
> clusterExport(cluster, "base")
> parSapply(cluster, desde:hasta, function(x) base^x)
[1] 27 81 243 729 2187 6561
> stopCluster(cluster)
Otra forma de realizar cálculos paralelizados es con el
paquete foreach
que no viene instalado
por omisión sino requiere instalación manual:
> library(foreach)
Error in library(foreach) : there is no package called ‘foreach’
> install.packages("foreach")
also installing the dependency ‘iterators’
trying URL 'https://cran.itam.mx/bin/macosx/mavericks/contrib/3.3/iterators_1.0.8.tgz'
Content type 'application/x-gzip' length 310135 bytes (302 KB)
==================================================
downloaded 302 KB
trying URL 'https://cran.itam.mx/bin/macosx/mavericks/contrib/3.3/foreach_1.4.3.tgz'
Content type 'application/x-gzip' length 382270 bytes (373 KB)
==================================================
downloaded 373 KB
The downloaded binary packages are in
/var/folders/qj/1gw209fx1qb31lz61njz69k00000gn/T//RtmphnuSlq/downloaded_packages
Es importante tomar en cuenta que los números de los servidores de descarga no siempre son los mismos cuando uno instala poquetes en computoradoras diferentes o en momentos diferentes: siempre busco el más cercano a mi y tecleo la selección. Al utilizar R en un ambiente de interfaz gráfica, típicamente se abre una ventana para realizar la selección del servidor.
También se requiere un segundo paquete:
> install.packages("doParallel")
trying URL 'https://cran.itam.mx/bin/macosx/mavericks/contrib/3.3/doParallel_1.0.10.tgz'
Content type 'application/x-gzip' length 181113 bytes (176 KB)
==================================================
downloaded 176 KB
The downloaded binary packages are in
/var/folders/qj/1gw209fx1qb31lz61njz69k00000gn/T//RtmphnuSlq/downloaded_packages
Con el paquete foreach
, la creación y
liberación de un cluster es ligeramente diferente:
> library(doParallel)
Loading required package: foreach
foreach: simple, scalable parallel programming from Revolution Analytics
Use Revolution R for scalability, fault tolerance and more.
http://www.revolutionanalytics.com
Loading required package: iterators
> registerDoParallel(makeCluster(detectCores() - 1))
> foreach(potencia = desde:hasta, .combine=c) %dopar% base^potencia
[1] 27 81 243 729 2187 6561
> stopImplicitCluster()
En las llamadas a foreach
, se puede proprocionar
opciones .export
y .packages
para
proporcionarle variables externas y librerías adicionales.
Tarjetas gráficas contemporaneas contienen procesdaores gráficos con
bastante núcleos. El paquete gpuR
permite aprovechar
esos núcleos para cálculos. Los tipos de dato para realizar cálculos
con un GPU son especiales y provienen de ese paquete. Finalizando la
instalación del paquete, igual como al cargarlo al uso, se detectan
GPUs presentes en la computadora en cuestión, por ejemplo, en una
computadora de escritorio iMac detecta
Number of platforms: 1
- platform: Apple: OpenCL 1.2 (Apr 4 2017 19:07:42)
- gpu index: 0
- AMD Radeon R9 M395X Compute Engine
Number of platforms: 1
- platform: Apple: OpenCL 1.2 (Apr 4 2017 19:07:42)
- gpu index: 0
- Intel(R) HD Graphics 5300
Por ejemplo, el producto de matrices que usa de prueba Charles Determan (el autor del paquete) en su blog post hace lo siguiente con el GPU de Radeon:
> suppressMessages(library(gpuR))
> n <- 2000
> M1 <- matrix(runif(n * n), nrow=n, ncol=n)
> M2 <- matrix(rnorm(n * n), nrow=n, ncol=n)
> system.time(M1 %*% M2)
user system elapsed
4.587 0.002 4.591
> G1 <- gpuMatrix(M1, type="double")
> G2 <- gpuMatrix(M2, type="double")
> system.time(G1 %*% G2)
user system elapsed
0.082 0.027 0.224
y esto con el GPU de Intel (hay que cambiar el double
por float
ya que es un GPU más modesto:
> suppressMessages(library(gpuR))
> n <- 2000
> M1 <- matrix(runif(n * n), nrow=n, ncol=n)
> M2 <- matrix(rnorm(n * n), nrow=n, ncol=n)
> system.time(M1 %*% M2)
user system elapsed
7.016 0.033 7.092
> G1 <- gpuMatrix(M1, type="float")
> G2 <- gpuMatrix(M2, type="float")
> system.time(G1 %*% G2)
user system elapsed
0.079 0.042 1.970
En otras computadoras con GPUs de NVIDIA se obtiene lo siguiente:
> library(gpuR)
Number of platforms: 1
- platform: NVIDIA Corporation: OpenCL 1.2 CUDA 8.0.0
- gpu index: 0
- GeForce GT 720
checked all devices
completed initialization
gpuR 1.2.1
> n <- 2000
> M1 <- matrix(runif(n * n), nrow=n, ncol=n)
> M2 <- matrix(rnorm(n * n), nrow=n, ncol=n)
> system.time(M1 %*% M2)
user system elapsed
4.09 0.02 4.11
> G1 <- gpuMatrix(M1, type="double")
> G2 <- gpuMatrix(M2, type="double")
> system.time(G1 %*% G2)
user system elapsed
0.41 1.31 1.72
> library(gpuR)
Number of platforms: 1
- platform: NVIDIA Corporation: OpenCL 1.2 CUDA 8.0.0
- gpu index: 0
- Tesla K80
checked all devices
completed initialization
gpuR 1.2.1
> n <- 2000
> M1 <- matrix(runif(n * n), nrow=n, ncol=n)
> M2 <- matrix(rnorm(n * n), nrow=n, ncol=n)
> system.time(M1 %*% M2)
user system elapsed
5.424 0.000 5.424
> G1 <- gpuMatrix(M1, type="double")
> G2 <- gpuMatrix(M2, type="double")
> system.time(G1 %*% G2)
user system elapsed
1.348 0.052 1.672
> library(gpuR)
Number of platforms: 1
- platform: NVIDIA Corporation: OpenCL 1.2 CUDA 11.1.102
- context device index: 0
- GeForce GTX 1650
checked all devices
completed initialization
gpuR 2.0.3
> n <- 2000
> M1 <- matrix(runif(n * n), nrow=n, ncol=n)
> M2 <- matrix(rnorm(n * n), nrow=n, ncol=n)
> system.time(M1 %*% M2)
user system elapsed
4.577 0.000 4.582
> G1 <- gpuMatrix(M1, type="double")
> G2 <- gpuMatrix(M2, type="double")
> system.time(G1 %*% G2)
user system elapsed
0.920 0.029 1.060
> library(gpuR)
library(gpuR)
Number of platforms: 1
- platform: NVIDIA Corporation: OpenCL 1.2 CUDA 11.1.102
- context device index: 0
- GeForce RTX 3090
checked all devices
completed initialization
gpuR 2.0.3
> n <- 2000
> M1 <- matrix(runif(n * n), nrow=n, ncol=n)
> M2 <- matrix(rnorm(n * n), nrow=n, ncol=n)
> system.time(M1 %*% M2)
user system elapsed
4.770 0.000 4.771
> G1 <- gpuMatrix(M1, type="double")
> G2 <- gpuMatrix(M2, type="double")
> system.time(G1 %*% G2)
user system elapsed
0.121 0.041 0.191
Participantes que cuentan con acceso a un GPU pueden consultar la documentación del paquete para mayor detalle en cuáles operaciones con matríces y vectores están disponibles para realizarse en el GPU. En todas las tareas, se aprecia el uso adecuado de cálculos GPU y son una alternativa válida a paralelismo en el CPU — también conviene incluir comparasiones del desempeño cuando ambas opciones están disponibles.
https://satuelisa.github.io/simulation/paralelo/R.html