Crear una secuencia de salida única a partir de otras tres secuencias producidas en paralelo

10

Tengo tres tipos de datos que están en diferentes formatos; Para cada tipo de datos, hay un script de Python que lo transforma en un único formato unificado.

Este script de Python es lento y está vinculado a la CPU (a un solo núcleo en una máquina multinúcleo), por lo que quiero ejecutar tres instancias del mismo, una para cada tipo de datos, y combinar su salida para pasarlo sort. Básicamente, equivalente a esto:

{ ./handle_1.py; ./handle_2.py; ./handle_3.py } | sort -n

Pero con los tres scripts ejecutándose en paralelo.

Encontré esta pregunta en la que GNU splitse estaba utilizando para hacer un round-robin de una secuencia estándar entre n instancias de un script que maneja la secuencia.

Desde la página man dividida:

-n, --number=CHUNKS
          generate CHUNKS output files.  See below
CHUNKS  may be:
 N       split into N files based on size of input
 K/N     output Kth of N to stdout
 l/N     split into N files without splitting lines
 l/K/N   output Kth of N to stdout without splitting lines
 r/N     like 'l'  but  use  round  robin  distributio

Entonces el r/Ncomando implica " sin dividir líneas ".

En base a esto, parece que la siguiente solución debería ser factible:

split -n r/3 -u --filter="./choose_script" << EOF
> 1
> 2
> 3
> EOF

Donde choose_scripthace esto:

#!/bin/bash
{ read x; ./handle_$x.py; }

Desafortunadamente, veo algunas líneas entremezcladas, y muchas líneas nuevas que no deberían estar allí.

Por ejemplo, si reemplazo mis scripts de Python con algunos scripts de bash simples que hacen esto:

#!/bin/bash
# ./handle_1.sh
while true; echo "1-$RANDOM"; done;

.

#!/bin/bash
# ./handle_2.sh
while true; echo "2-$RANDOM"; done;

.

#!/bin/bash
# ./handle_3.sh
while true; echo "3-$RANDOM"; done;

Veo esta salida:

1-8394

2-11238
2-22757
1-723
2-6669
3-3690
2-892
2-312511-24152
2-9317
3-5981

Esto es molesto: según el extracto de la página de manual que pegué anteriormente, debería mantener la integridad de la línea.

Obviamente, funciona si elimino el -uargumento, pero luego está almacenado en el búfer y me quedaré sin memoria, ya que amortigua la salida de todos menos uno de los scripts.

Si alguien tiene alguna idea aquí, sería muy apreciado. Estoy fuera de mi profundidad aquí.

Cera
fuente
Algunas personas en #bash en freenode sugirieron que generara los tres procesos y los creara en segundo plano, escribiendo en FD personalizados, luego recorriera esos FD y leyera líneas para ellos, pero no he descubierto cómo hacer que eso funcione. También me dijeron que mirara el coprocbuiltin en bash, aunque realmente no veo cómo se aplica.
Cera
1
¿Tienes que hacerlo sin archivos intermedios? ¿No podrías simplemente hacerlo job1.py > file1 & job2.py > file 2 & job3.py > file3 ; wait ; sort -n file1 file2 file3?
angus

Respuestas:

2

Intente usar la opción -u de GNU paralela.

echo "1\n2\n3" | parallel -u -IX ./handle_X.sh

Esto los ejecuta en paralelo, sin amortiguar la totalidad de ningún proceso.

flowblok
fuente
Estoy un poco confundido: ¿es el hecho Xde IXdecir -Ique X será la bandera para reemplazar, o está aplicando la -Xbandera, que aparentemente también tiene un significado relevante?
Cera
Hmph Estoy haciendo esto: parallel -u -X ./handle_{}.sh ::: "1" "2" "3"y, desafortunadamente, todavía veo algunos cambios de salida.
Cera
el primero: también puede usarlo parallel -u ./handle_{}.sh, pero prefiero cambiarlo, ya que los corchetes también tienen el significado de unir comandos (como en su pregunta).
flowblok
Parece que funciona para mí, mi grep no se daña : pastie.org/5113187 (¿está utilizando los scripts de bash de prueba o sus scripts de Python reales?)
flowblok
El problema es que eso no está haciendo nada en paralelo. Estoy usando los scripts de bash - pastie.org/5113225
Cera
2

Tratar:

parallel ::: ./handle_1.py ./handle_2.py ./handle_3.py

Si handle_1.pytoma un nombre de archivo:

parallel ::: ./handle_1.py ./handle_2.py ./handle_3.py ::: files*

No desea que la salida se mezcle, así que no use -u.

Si desea mantener el orden (por lo que toda la salida de handle_1 es anterior a handle_2 y, por lo tanto, puede evitar la clasificación):

parallel -k  ::: ./handle_1.py ./handle_2.py ./handle_3.py ::: files*

Si aún desea ordenarlo, puede paralelizar el orden y utilizar sort -m:

parallel --files "./handle_{1}.py {2} | sort -n"  ::: 1 2 3 ::: files* | parallel -j1 -X sort -m

Establezca $ TMPDIR en un directorio que sea lo suficientemente grande como para contener la salida.

Ole Tange
fuente
1
Sí quiero que la salida sea "mixta". Solo quiero asegurarme de que cada línea de la salida final sea una sola línea de uno de los subprocesos. Si no lo mezclo, el sistema se quedará sin memoria almacenando en búfer las secuencias estándar que aún no se están imprimiendo.
Cera
Con GNU Parallel no se quedará sin memoria: no se almacena en la memoria intermedia. ¿Por qué crees que se almacena en la memoria?
Ole Tange
2

Tal vez me estoy perdiendo algo, pero no puedes simplemente hacer:

(./handle_1.py & ./handle_2.py & ./handle_3.py) | sort -n

Si desea que las líneas de cada proceso no se intercalen, lo más fácil es asegurarse de que el proceso mismo las escriba por completo y posiblemente deshabilite el almacenamiento en búfer de salida, ya writeque se garantiza que los valores atómicos de una tubería sean atómicos siempre que no sean más grandes que PIPE_BUF. Por ejemplo, usted podría asegurarse de que hace búfer de salida a la utilización stdioy la llamada fflusho lo que es el equivalente en pythondespués de una o unas pocas líneas se han escrito.

Si no puede modificar los scripts de Python, puede hacer lo siguiente:

lb() { grep --line-buffered '^'; }

(con GNU grep) o:

lb() while IFS= read -r l; do printf '%s\n' "$l"; done

(Consulte las notas en los comentarios a continuación si la salida de los comandos no es texto)

Y hacer:

(./handle_1.py | lb & ./handle_2.py | lb & ./handle_3.py | lb) | sort -n

Otra opción para evitar esos 3 lbprocesos es tener tres canales para un comando que use select/ pollpara ver de dónde proviene alguna salida y alimentarla en sortlínea, pero requiere un poco de programación.

Stéphane Chazelas
fuente
Necesitas una waitallí, creo.
derobert
1
No, a menos que algunos de los programas cierren su stdout antes de salir, porque la tubería y sort -npermanecerá hasta que todos los programas que tienen un fd abierto hayan salido.
Stéphane Chazelas
De hecho, lo probé, tienes razón.
derobert
No, todavía recibo resultados destrozados. Las líneas se mezclan y se intercalan.
Cera
1
OK @Cerales, mira mi respuesta actualizada
Stéphane Chazelas
1

La respuesta de Flowbok fue la solución correcta. Curiosamente, la salida de GNU parallelse destruye si se envía directamente a un archivo, pero no si va a un tty.

Afortunadamente, script -cestá disponible para imitar un tty.

Todavía quedan los tres guiones:

#!/bin/bash
# handle_1.sh
while true; do echo "1-$RANDOM$RANDOM$RANDOM$RANDOM"; done

.

#!/bin/bash
# handle_2.sh
while true; do echo "2-$RANDOM$RANDOM$RANDOM$RANDOM"; done

.

#!/bin/bash
# handle_3.sh
while true; do echo "3-$RANDOM$RANDOM$RANDOM$RANDOM"; done

Luego hay un archivo que encapsula la llamada al paralelo:

#!/bin/bash
# run_parallel.sh
parallel -u -I N ./handle_N.sh ::: "1" "2" "3"

Y luego lo llamo así:

script -c ./run_parallel > output

Las líneas en la salida se mezclan línea por línea entre la salida de los diferentes scripts, pero no se mutilan ni se intercalan en una línea determinada.

Comportamiento extraño de parallel: puedo presentar un informe de error.

Cera
fuente