Cómo optimizar mi script R para usar "multinúcleo"

15

Estoy usando GNU R en una PC Ubuntu-Lucid que tiene 4 CPU. Para utilizar las 4 CPU, instalé el paquete "r-cran-multicore". Como el manual del paquete carece de ejemplos prácticos que entiendo, necesito consejos sobre cómo optimizar mi script para utilizar las 4 CPU.

Mi conjunto de datos es un data.frame (llamado P1) que tiene 50,000 filas y 1600 cols. Para cada fila, me gustaría calcular el máximo, la suma y la media. Mi guión se ve de la siguiente manera:

p1max <- 0
p1mean <- 0
p1sum <-0
plength <- length(P1[,1])
for(i in 1:plength){
   p1max <- c(p1max, max(P1[i,]))
   p1mean <- c(p1mean, mean(P1[i,]))
   p1sum <- c(p1sum, sum(P1[i,]))
}

¿Podría alguien decirme cómo modificar y ejecutar el script para usar las 4 CPU?

Produnis
fuente
hay un error en el programa anterior: la línea debe ser "for (i in 1: plength)"
Simon Byrne
eres justo, gracias!
Produnis
1
¿No pertenece esto en StackOverflow?
R_Coholic
1
Esto pertenece a StackOverflow. No hay preguntas de estadísticas aquí en absoluto. Solo una pregunta de programación general.
JD Long

Respuestas:

11

Use foreach y doMC . La explicación detallada se puede encontrar aquí . Tu guión cambiará muy poco, la línea

for(i in 1:plength){

debe cambiarse a

foreach(i=1:plength) %dopar% { 

Los requisitos previos para cualquier script multitarea que use estos paquetes son

library(foreach)
library(doMC)
registerDoMC()

Nota de precaución. De acuerdo con la documentación, no puede usar esto en la GUI.

En cuanto a su problema, ¿realmente necesita multitarea? Su data.frame toma aproximadamente 1.2 GB de RAM, por lo que debe caber en su memoria. Entonces, simplemente puede usar apply:

p1smry <- apply(P1,1,summary)

El resultado será una matriz con resúmenes de cada fila.

También puede usar la función mclapply que se encuentra en el paquete multinúcleo. Entonces su script podría verse así:

loopfun <- function(i) {
     summary(P1[i,])
}

res <- mclapply(1:nrow(P1),loopfun)

Esto devolverá la lista, donde i-ésimo elemento será el resumen de la i-ésima fila. Puedes convertirlo a matriz usando sapply

mres <- sapply(res,function(x)x)
mpiktas
fuente
muchas gracias. Tienes razón, que con "aplicar" el script podría optimizarse. Acabo de usar mi script como un ejemplo mínimo para transmitir el mensaje ... Muchas gracias, ¡tu respuesta es exactamente lo que estaba buscando!
Produnis
15

Ya tiene una respuesta sobre cómo usar más de un núcleo, pero el verdadero problema es la forma en que ha escrito sus bucles. Nunca extienda su vector / objeto de resultado en cada iteración de un bucle . Si haces esto, obligas a R a copiar tu vector / objeto de resultado y lo extiendes, lo que lleva tiempo. En su lugar, asigne previamente suficiente espacio de almacenamiento antes de comenzar el ciclo y complete a medida que avanza. Aquí hay un ejemplo:

set.seed(1)
p1 <- matrix(rnorm(10000), ncol=100)
system.time({
p1max <- p1mean <- p1sum <- numeric(length = 100)
for(i in seq_along(p1max)){
   p1max[i] <- max(p1[i,])
   p1mean[i] <- mean(p1[i,])
   p1sum[i ]<- sum(p1[i,])
}
})

   user  system elapsed 
  0.005   0.000   0.005

O puede hacer estas cosas a través de apply():

system.time({
p1max2 <- apply(p1, 1, max)
p1mean2 <- apply(p1, 1, mean)
p1sum2 <- apply(p1, 1, sum)
})
   user  system elapsed 
  0.007   0.000   0.006 

Pero tenga en cuenta que esto no es más rápido que hacer el bucle correctamente y, a veces, más lento.

Sin embargo, siempre esté atento al código vectorizado. Puede hacer sumas de filas y medios usando rowSums()y rowMeans()que son más rápidos que el bucle o las applyversiones:

system.time({
p1max3 <- apply(p1, 1, max)
p1mean3 <- rowMeans(p1)
p1sum3 <- rowSums(p1)
})

   user  system elapsed 
  0.001   0.000   0.002 

Si fuera un apostador, tendría dinero en el tercer enfoque que menciono golpear foreach()u otras opciones multinúcleo en una prueba de velocidad en su matriz porque tendrían que acelerar las cosas considerablemente para justificar los gastos generales incurridos al configurar el procesos separados que se cultivan en los diferentes núcleos de CPU.

Actualización: Siguiendo el comentario de @shabbychef, ¿es más rápido hacer las sumas una vez y reutilizarlas en el cálculo de la media?

system.time({
    p1max4 <- apply(p1, 1, max)
    p1sum4 <- rowSums(p1)
    p1mean4 <- p1sum4 / ncol(p1)
    })

   user  system elapsed 
  0.002   0.000   0.002

No en esta prueba, pero esto está lejos de ser exhaustivo ...

Restablece a Mónica - G. Simpson
fuente
FWIW, Matlab tiene los mismos problemas con respecto a la preasignación y la expansión de vectores, y es un código clásico 'blooper'. Además de su apuesta, probablemente sea más rápido usar los resultados rowSumspara calcular los medios de la fila (a menos que me falte algo con respecto a, por ejemplo, Na o NaN). El código en su tercer enfoque suma cada columna dos veces .
shabbychef
@shabbychef te sorprenderás (ver mi respuesta editada). Si las sumas se calculan teóricamente dos veces, pero rowSumsy el rowMeanscódigo compilado y lo que se gana en un solo cálculo de las sumas una vez que están altamente optimizadas, nos suelta de nuevo al hacer el cálculo promedio en código interpretado.
Restablece a Monica - G. Simpson el
@Gavin Simpson: no tan rápido: intente en su lugar system.time({ for (iii in c(1:1000)) { p1max3 <- apply(p1, 1, max) p1mean3 <- rowMeans(p1) p1sum3 <- rowSums(p1) } })y de manera similar system.time({ for (iii in c(1:1000)) { p1max4 <- apply(p1, 1, max) p1sum4 <- rowSums(p1) p1mean4 <- p1sum4 / ncol(p1) } }); la versión que no vuelve a calcular la suma tarda 1.368 segundos en mi computadora; el que sí toma 1.396. de nuevo, lejos de ser exhaustivo, pero más convincente ...
shabbychef
@shabbychef debemos tener diferentes ideas sobre lo que es o no es convincente ;-) De hecho, las simulaciones más rigurosos refuerzan mi punto principal, que a medida rowMeansy rowSumsse aplican en código eficiente, optimizado compilado que van a ser difícil de superar.
Restablece a Monica - G. Simpson el
@Gavin Simpson. En realidad, el problema con mi ejemplo es que la mayor parte del tiempo se toma en la parte de aplicación para calcular el máximo. Estoy de acuerdo con usted en que una función vectorizada basada en c como rowMeanserá difícil de superar a través de una herramienta R de propósito general como *apply. Sin embargo, parece sugerir que es más rápido sumar 10000 números dos veces a través rowMeany en rowSumlugar de solo una vez, y usar el operador de división incorporado de R. Sé que R tiene algunos problemas de eficiencia ( por ejemplo, el reciente descubrimiento del problema de las llaves y los paréntesis), pero eso parece una locura.
shabbychef
1

Echa un vistazo a los paquetes de nieve y nieve . Un montón de ejemplos con esos ...

Si desea acelerar ese código específico en lugar de aprender sobre R y el paralelismo, debe hacerlo

P1 = matrix(rnorm(1000), ncol=10, nrow=10
apply(P1, 1, max)
apply(P1, 1, mean)
apply(P1, 1, sum)
Dr. G
fuente
por favor
ayúdenme
2
Esos solo te están ocultando el bucle. El verdadero problema con el código @Produnis es que se está realizando una copia forzada porque los vectores de resultados se extienden en cada iteración del bucle.
Restablece a Monica - G. Simpson el
El paquete de nevadas puede extender la solución de Gavin como decir "pastel". El paquete tiene una gran cantidad de funciones de aplicación modificadas para hacer multicorneamiento. Para la función de aplicación, usaría sfApply (<sus argumentos como para aplicar>). Las nevadas también están bien documentadas. Debo señalar que no se necesita ningún software adicional para realizar esto en un procesador multinúcleo. Consulte stackoverflow.com/questions/4164960/… para ver un ejemplo de sfLapply.
Roman Luštrik