Ejecutando comandos canalizados en paralelo

16

Considere el siguiente escenario. Tengo dos programas A y B. El programa A sale a líneas de cadenas estándar mientras que el programa B procesa líneas desde stdin. La forma de usar estos dos programas es, por supuesto:

foo @ bar: ~ $ A | si

Ahora he notado que esto solo consume un núcleo; Por eso me pregunto:

¿Los programas A y B comparten los mismos recursos computacionales? Si es así, ¿hay alguna manera de ejecutar A y B al mismo tiempo?

Otra cosa que he notado es que A se ejecuta mucho más rápido que B, por lo tanto, me pregunto si de alguna manera podría ejecutar más programas B y dejar que procesen las líneas que A genera en paralelo.

Es decir, A generaría sus líneas, y habría N instancias de programas B que leerían estas líneas (quien las lea primero) las procesaría y generaría en stdout.

Entonces mi pregunta final es:

¿Hay alguna manera de canalizar la salida a A entre varios procesos B sin tener que ocuparse de las condiciones de carrera y otras inconsistencias que podrían surgir?

Jernej
fuente
1
Si bien A | B | Ces paralelo como en procesos separados, debido a la naturaleza de las tuberías (B tiene que esperar la salida de A, C tiene que esperar la salida de B) aún puede ser lineal en algunos casos. Depende completamente de qué tipo de salida producen. No hay muchos casos en los que la ejecución de múltiples Bayude mucho, es muy posible que el ejemplo de wc paralelo sea más lento que el normal, wcya que dividir puede requerir más recursos que contar líneas normalmente. Usar con cuidado.
frostschutz

Respuestas:

14

Un problema con split --filteres que la salida se puede mezclar, por lo que obtiene media línea del proceso 1 seguido de media línea del proceso 2.

GNU Parallel garantiza que no habrá confusión.

Así que asume que quieres hacer:

 A | B | C

Pero ese B es terriblemente lento y, por lo tanto, quieres paralelizar eso. Entonces puedes hacer:

A | parallel --pipe B | C

GNU Paralelo se divide por defecto en \ ny un tamaño de bloque de 1 MB. Esto se puede ajustar con --recend y --block.

Puede encontrar más información sobre GNU Parallel en: http://www.gnu.org/s/parallel/

Puede instalar GNU Parallel en solo 10 segundos con:

wget -O - pi.dk/3 | sh 

Vea el video de introducción en http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ole Tange
fuente
1
Si bien no estoy de acuerdo con el método de instalación :-), +1 porque su solución resuelve la mayoría de los problemas con la mía.
LSerni
Este es realmente bueno. ¿También tiene alguna sugerencia para los parámetros que se utilizarán? Sé que el programa A generará más de 1 TB de datos de aproximadamente 5 GB por minuto. El programa B procesa los datos 5 veces más lento que A los genera y tengo 5 núcleos a mi disposición para esta tarea.
Jernej
GNU Parallel actualmente puede manejar como máximo alrededor de 100 MB / s, por lo que va a tocar ese límite. Lo óptimo --block-sizedependerá de la cantidad de RAM y de la rapidez con que pueda comenzar una nueva B. En su situación, usaría --block 100My vería cómo funciona.
Ole Tange
@lserni ¿Puede encontrar un método de instalación que sea mejor, que funcione en la mayoría de las máquinas UNIX y requiera una cantidad similar de trabajo por parte del usuario?
Ole Tange
44
Lo siento, no me hice claro. El método de instalación, al que se pasó el script sh, es excelente. El problema radica en pasarlo a sh: descargar y ejecutar código ejecutable desde un sitio . Eso sí, tal vez solo estoy siendo demasiado paranoico, ya que uno podría objetar que un RPM o DEB personalizado es básicamente lo mismo, e incluso publicar el código en una página para copiarlo y pegarlo resultaría en que las personas lo hagan a ciegas de todas formas.
LSerni
13

Cuando escribe A | B, ambos procesos ya se ejecutan en paralelo. Si ve que usan solo un núcleo, probablemente se deba a la configuración de afinidad de la CPU (quizás haya alguna herramienta para generar un proceso con diferente afinidad) o porque un proceso no es suficiente para mantener un núcleo completo, y el sistema " prefiere "no extender la informática.

Para ejecutar varias B con una A, necesita una herramienta como splitcon la --filteropción:

A | split [OPTIONS] --filter="B"

Sin embargo, esto puede estropear el orden de las líneas en la salida, porque los trabajos B no se ejecutarán a la misma velocidad. Si esto es un problema, es posible que deba redirigir la salida B i-th a un archivo intermedio y unirlas al final usando cat. Esto, a su vez, puede requerir un considerable espacio en disco.

Existen otras opciones (por ejemplo, se podría limitar a cada instancia de B a una sola línea de salida de búfer, espere hasta que su conjunto "redondo" de B ha finalizado, ejecute el equivalente de una a reducir a split's mapa , y catla salida temporal juntos), con diferentes niveles de eficiencia. La opción 'redonda' que se acaba de describir, por ejemplo, esperará a que finalice la instancia más lenta de B , por lo que dependerá en gran medida del almacenamiento en búfer disponible para B; [m]bufferpuede ayudar, o puede que no, dependiendo de cuáles sean las operaciones.

Ejemplos

Genere los primeros 1000 números y cuente las líneas en paralelo:

seq 1 1000 | split -n r/10 -u --filter="wc -l"
100
100
100
100
100
100
100
100
100
100

Si tuviéramos que "marcar" las líneas, veríamos que cada primera línea se envía al proceso # 1, cada quinta línea al proceso # 5 y así sucesivamente. Además, en el tiempo que lleva splitgenerar el segundo proceso, el primero ya es un buen camino hacia su cuota:

seq 1 1000 | split -n r/10 -u --filter="sed -e 's/^/$RANDOM - /g'" | head -n 10
19190 - 1
19190 - 11
19190 - 21
19190 - 31
19190 - 41
19190 - 51
19190 - 61
19190 - 71
19190 - 81

Cuando se ejecuta en una máquina de 2 núcleos seq, splity los wcprocesos comparten los núcleos; pero mirando más de cerca, el sistema deja los dos primeros procesos en CPU0 y divide la CPU1 entre los procesos de trabajo:

%Cpu0  : 47.2 us, 13.7 sy,  0.0 ni, 38.1 id,  1.0 wa,  0.0 hi,  0.0 si,  0.0 st
%Cpu1  : 15.8 us, 82.9 sy,  0.0 ni,  1.0 id,  0.0 wa,  0.3 hi,  0.0 si,  0.0 st
  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM     TIME+ COMMAND
 5314 lserni    20   0  4516  568  476 R 23.9  0.0   0:03.30 seq
 5315 lserni    20   0  4580  720  608 R 52.5  0.0   0:07.32 split
 5317 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5318 lserni    20   0  4520  572  484 S 14.0  0.0   0:01.88 wc
 5319 lserni    20   0  4520  576  484 S 13.6  0.0   0:01.88 wc
 5320 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.85 wc
 5321 lserni    20   0  4520  572  484 S 13.3  0.0   0:01.84 wc
 5322 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5323 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.86 wc
 5324 lserni    20   0  4520  576  484 S 13.3  0.0   0:01.87 wc

Observe especialmente que se splitestá comiendo una cantidad considerable de CPU. Esto disminuirá en proporción a las necesidades de A; es decir, si A es un proceso más pesado que seq, la sobrecarga relativa de splitdisminuirá. Pero si A es un proceso muy liviano y B es bastante rápido (por lo que no necesita más de 2-3 B para mantenerse junto con A), entonces la paralelización con split(o tuberías en general) bien podría no valer la pena.

LSerni
fuente
Es interesante que la división encontrada en Ubuntu no tenga la opción --filter. ¿Qué tipo de sistema operativo está utilizando para esto?
Jernej
Linux OpenSuSE 12.3, con coreutils ( gnu.org/software/coreutils/manual/html_node/… ). Trataré de conseguir un Ubuntu, podrían haber cambiado el nombre para acomodar alguna herramienta con un nombre similar.
LSerni
¿Estás seguro de que split --filterfalta la opción? En mi Ubuntu 12.04-LTS ("wheezy / sid"), está ahí, y mis ejemplos funcionan. ¿Podría haber instalado uno diferente splital de GNU coreutils?
LSerni
Gracias por esto. Tuve que instalar una versión más nueva de Coreutils. Por cierto, he notado que si ejecuto el programa A solo, se come un núcleo completo (100%) si ejecuto A | B entonces juntos comen un núcleo entero, procesan A comiendo 15% y procesan B comiendo 85% .. ¿Ves por qué esto es así?
Jernej
2
Esto es probable debido al bloqueo . Si B es más pesado que A, entonces A no puede enviar su salida y se ralentiza. Otra posibilidad es que A ceda a B durante su funcionamiento (por ejemplo, disco / red). En un sistema diferente, es posible que vea B engullendo el 100% de la CPU1 y A asignándose el 18% de la CPU0. Probablemente necesite 85/15 ~ 5.67 = entre 5 y 6 instancias de B para obtener una sola instancia de A para saturar un solo núcleo. Sin embargo, I / O, si está presente, podría sesgar estos valores.
LSerni