2012-05-02 7 views
10

Estoy jugando con la paralelización en R por primera vez. Como primer ejemplo de juguete, probé¿Por qué foreach()% do% a veces es más lento que para?

library(doMC) 
registerDoMC() 

B<-10000 

myFunc<-function() 
{ 
    for(i in 1:B) sqrt(i) 
} 

myFunc2<-function() 
{ 
    foreach(i = 1:B) %do% sqrt(i) 
} 

myParFunc<-function() 
{ 
    foreach(i = 1:B) %dopar% sqrt(i) 
} 

Sé que sqrt() ejecuta demasiado rápido para parallellization a la materia, pero lo que no esperaba era que foreach() %do% sería más lento que for():

> system.time(myFunc()) 
    user system elapsed 
    0.004 0.000 0.005 
> system.time(myFunc2()) 
    user system elapsed 
    6.756 0.000 6.759 
> system.time(myParFunc()) 
    user system elapsed 
    6.140 0.524 6.096 

En la mayoría de los ejemplos que he visto, foreach() %dopar% se compara con foreach() %do% en lugar de for(). Dado que foreach() %do% fue mucho más lento que for() en mi ejemplo de juguete, ahora estoy un poco confundido. De alguna manera, pensé que estas eran formas equivalentes de construir for-loops. ¿Cuál es la diferencia? ¿Alguna vez son equivalentes? ¿Es foreach() %do% siempre más lento?

Actualización: Después de multas @Peter responden, puedo actualizar myFunc de la siguiente manera:

a<-rep(NA,B) 
myFunc<-function() 
{ 
    for(i in 1:B) a[i]<-sqrt(i) 
} 

Esto hace for() un poco más lento, pero no mucho:

> system.time(myFunc()) 
    user system elapsed 
    0.036 0.000 0.035 
> system.time(myFunc2()) 
    user system elapsed 
    6.380 0.000 6.385 
+1

Consulte también esta pregunta: http://stackoverflow.com/questions/5007458/problems-using-foreach-parallelization y este: http: // stackoverflow.com/questions/5012804/mpi-parallelization-using-snow-is-slow – Charlie

+0

Gracias @Charlie, ¡las respuestas a esas preguntas fueron muy útiles para lo que estoy intentando hacer después de que termine con mi ejemplo de juguete! :) Todavía no estoy seguro de entender por qué 'foreach' necesita mucho más tiempo cuando se usa la opción'% do% '. –

+0

Una gran parte de esto es que% do% tiene que parcelar las piezas/asignaciones, enviarlas a los procesadores, luego volver a unirlas al final según corresponda. Estos pasos requieren tiempo de organización que no tiene la versión sin paralelo. – Charlie

Respuesta

8

for se ejecutará sqrt B veces, presumiblemente descartando la respuesta cada vez. foreach, sin embargo, devuelve una lista que contiene el resultado de cada ejecución del cuerpo del bucle. Esto contribuiría a una considerable sobrecarga adicional, independientemente de si se está ejecutando en modo paralelo o secuencial (%dopar% o %do%).

Basé mi respuesta ejecutando el siguiente código, que parece ser confirmado por foreach vignette, que dice "foreach difiere de un ciclo for en que su retorno es una lista de valores, mientras que un ciclo for no tiene valor y usa efectos secundarios para transmitir su resultado ".

> print(for(i in 1:10) sqrt(i)) 
NULL 

> print(foreach(i = 1:10) %do% sqrt(i)) 
[[1]] 
[1] 1 

[[2]] 
[1] 1.414214 

[[3]] 
... etc 

ACTUALIZACIÓN: Veo en su pregunta actualizada que la respuesta anterior no es suficiente para explicar la diferencia en el rendimiento. ¡Así que miré el source code para foreach y puedo ver que hay MUCHO en marcha! No he intentado comprender exactamente cómo funciona, sin embargo, do.R y foreach.R muestran que incluso cuando se ejecuta %do%, aún se ejecutan grandes partes de la configuración foreach, lo que tendría sentido si la opción %do% se proporciona en gran medida para permitirle pruebe el código foreach sin tener que tener un servidor paralelo configurado y cargado. También necesita admitir las funciones de iteración e iteración más avanzadas que proporciona el foreach.

Hay referencias en el código de caché de resultados, comprobación de errores, depuración y la creación de variables de entorno local para los argumentos de cada iteración (consulte la función doSEQ en do.R por ejemplo). Me imagino que esto es lo que crea la diferencia que has observado. Por supuesto, si estuviera ejecutando un código mucho más complicado dentro de su bucle (que realmente se beneficiaría de un marco de paralelización como foreach), esta sobrecarga sería irrelevante en comparación con los beneficios que proporciona.

+0

Derecha: eso debería explicar al menos parte de la diferencia. Todavía no estoy seguro de si lo explica todo; ver la actualización de mi pregunta! –

+0

@MansT, ¿mi actualización ayuda a explicar las cosas? –

+0

Sí, gracias, eso explica mucho :) –

Cuestiones relacionadas