Bucles de concha paralela


11

Quiero procesar muchos archivos y desde que tengo aquí un montón de núcleos, quiero hacerlo en paralelo:

for i in *.myfiles; do do_something $i `derived_params $i` other_params; done

Sé de una solución Makefile pero mis comandos necesitan los argumentos de la lista global de shell. Lo que encontré es:

> function pwait() {
>     while [ $(jobs -p | wc -l) -ge $1 ]; do
>         sleep 1
>     done
> }
>

Para usarlo, todo lo que hay que hacer es poner y después de los trabajos y una llamada en espera, el parámetro proporciona la cantidad de procesos paralelos:

> for i in *; do
>     do_something $i &
>     pwait 10
> done

Pero esto no funciona muy bien, por ejemplo, lo probé con, por ejemplo, un bucle for que convierte muchos archivos, pero me dio un error y dejó los trabajos sin hacer.

No puedo creer que esto aún no se haya hecho ya que la discusión sobre la lista de correo de zsh es muy antigua por ahora. Entonces, ¿sabes algo mejor?


Similar a esta pregunta: superuser.com/questions/153630/… Vea si esa técnica funciona para usted.
JRobert

Sería útil si publicaras los mensajes de error.
Pausado hasta nuevo aviso.

@JRobert, sí, lo sabía, pero esto en realidad no ayuda, ya que el enfoque de makefile no funcionará como dije. @ Dennis: Ok, primero dejé correr una parte superior junto a mostrarme más que el número especificado de procesos. En segundo lugar, no vuelve a la solicitud correctamente. En tercer lugar, dije que deja los trabajos sin hacer no estaba bien: acabo de colocar un indicador echo "DONE"después del ciclo que se ejecutó antes de que los trabajos activos no finalicen. => Esto me hizo pensar que los trabajos no estaban hechos.
matemáticas

Respuestas:


15

Un archivo MAKE es una buena solución a su problema. Podrías programar esta ejecución paralela en un shell, pero es difícil, como has notado. Una implementación paralela de make no solo se encargará de iniciar trabajos y detectar su finalización, sino que también se encargará del equilibrio de carga, lo cual es complicado.

El requisito de globbing no es un obstáculo: hay implementaciones de make que lo soportan. GNU make, que tiene expansión de comodines como $(wildcard *.c)y acceso de shell como $(shell mycommand)(funciones de búsqueda en el manual de GNU make para obtener más información). Es el valor predeterminado makeen Linux y está disponible en la mayoría de los otros sistemas. Aquí hay un esqueleto de Makefile que puede adaptar a sus necesidades:

fuentes = $ (comodín * .src)

todos: $ (fuentes: .src = .tgt)

% .tgt: $ .src
    hacer_algo $ <$$ (derivados_parámetros $ <)> $ @

Ejecute algo como make -j4ejecutar cuatro trabajos en paralelo o make -j -l3mantener el promedio de carga alrededor de 3.


8

No estoy seguro de cómo son sus argumentos derivados. Pero con GNU Parallel http: // www.gnu.org/software/parallel/ puede hacer esto para ejecutar un trabajo por núcleo de CPU:

find . | parallel -j+0 'a={}; name=${a##*/}; upper=$(echo "$name" | tr "[:lower:]" "[:upper:]");
   echo "$name - $upper"'

Si lo que desea obtener es simplemente cambiar la extensión. {.} Puede ser útil:

parallel -j+0 lame {} -o {.}.mp3 ::: *.wav

Mire el video de introducción a GNU Parallel en http://www.youtube.com/watch?v=OpaiGYxkSuQ


7

¿No usaría el waitcomando del shell para usted?

for i in *
do
    do_something $i &
done
wait

Su ciclo ejecuta un trabajo, luego lo espera y luego realiza el siguiente trabajo. Si lo anterior no funciona para usted, entonces el suyo podría funcionar mejor si se muda pwaitdespués done.


no, con 1 millón de archivos, tendría 1 millón de procesos ejecutándose, ¿o me equivoco?
Matemáticas

1
@brubelsabs: Bueno, intentaría hacer un millón de procesos. No dijo en su pregunta cuántos archivos necesitaba procesar. Creo que necesitaría usar forbucles anidados para limitar eso: for file in *; do for i in {1..10}; do do_something "$i" & done; wait; done(no probado) Eso debería hacer diez a la vez y esperar hasta que se terminen los diez de cada grupo antes de comenzar los siguientes diez. Su ciclo hace uno a la vez haciendo la &discusión. Vea la pregunta que JRobert enlazó para otras opciones. Busque en Stack Overflow otras preguntas similares a las suyas (y esa).
Pausado hasta nuevo aviso.

Si el OP anticipa un millón de archivos, entonces tendría un problema for i in *. Tendría que pasar argumentos al bucle con una tubería o algo así. Luego, en lugar de un bucle interno, puede ejecutar un contador incremental y ejecutar "micro-"wait"-s"cada "$ ((i% 32))" -eq '0'

@DennisWilliamson: combinar waitcon un contador interno funcionó bien para mí. ¡Gracias!
Joel Purra

3

¿Por qué nadie ha mencionado xargs todavía?

Asumiendo que tienes exactamente tres argumentos,

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; done | xargs -n 3 -P $PROCS do_something

De lo contrario, use un delimitador (nulo es útil para eso):

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; echo -ne "\0"; done | xargs -0 -n 1 -P $PROCS do_something

EDITAR: para lo anterior, cada parámetro debe estar separado por un carácter nulo, y luego el número de parámetros debe especificarse con xargs -n.


Sí, en nuestro proyecto alguien ha tenido la misma idea, y funciona muy bien incluso bajo Windows con MSys.
matemáticas

0

Intenté algunas de las respuestas. Hacen que el script sea un poco más complejo de lo que se necesita. Lo ideal sería utilizar parallelo xargssería preferible, sin embargo, si las operaciones dentro del ciclo for son complicadas, podría ser problemático crear archivos de líneas grandes y largas para suministrar en paralelo. en su lugar podríamos usar la fuente de la siguiente manera

# Create a test file 
$ cat test.txt
task_test 1
task_test 2

# Create a shell source file 
$ cat task.sh
task_test()
{
    echo $1
}

# use the source under bash -c 
$ cat test.txt | xargs -n1 -I{} bash -c 'source task.sh; {}'
1
2

Por lo tanto, para su problema, la solución se vería así

for i in *.myfiles; echo " do_something $i `derived_params $i` other_params
" >> commands.txt ; done

definir hacer algo como do_something.sh

do_something(){
process $1
echo $2 
whatever $3 

}

ejecutar con xargognu parallel

   cat commands.txt | xargs -n1 -I{} -P8 bash -c 'source do_something.sh; {}'

Supongo que la independencia funcional de las iteraciones de for está implícita.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.