Criando um único fluxo de saída a partir de três outros fluxos produzidos em paralelo

9

Eu tenho três tipos de dados que estão em diferentes formatos; para cada tipo de dados, existe um script Python que o transforma em um único formato unificado.

Este script Python é lento e limitado pela CPU (para um único núcleo em uma máquina com vários núcleos), portanto, quero executar três instâncias dele - uma para cada tipo de dados - e combinar sua saída para passá-la para% código%. Basicamente, equivalente a isto:

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

Mas com os três scripts sendo executados em paralelo.

Eu encontrei esta questão onde o GNU sort estava sendo usado para round-robin algum fluxo stdout entre n instâncias de um script que lida com o fluxo.

A partir da página do manual de divisão:

-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

Portanto, o comando split implica em " sem separar linhas ".

Com base nisso, parece que a solução a seguir deve ser viável:

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

Onde r/N faz isso:

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

Infelizmente, vejo algumas misturas de linhas - e muitas linhas novas que não deveriam estar lá.

Por exemplo, se eu substituir meus scripts Python por alguns scripts bash simples que fazem isso:

#!/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;

Eu vejo esta saída:

1-8394

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

Isso é irritante - com base no extrato da página man que colei acima, ele deve manter a integridade da linha.

Obviamente, funciona se eu remover o argumento choose_script , mas ele está em buffer e eu ficarei sem memória, pois ele armazena a saída de todos os scripts, exceto um.

Se alguém tiver alguma idéia aqui, seria muito apreciado. Estou fora da minha profundidade aqui.

    
por Cera 25.10.2012 / 07:44

4 respostas

2

Tente usar a opção -u do GNU paralelo.

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

Isso os executa em paralelo, sem armazenar todo o processo.

    
por 25.10.2012 / 08:09
2

Tente:

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

Se handle_1.py tiver um nome de arquivo:

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

Você não deseja a saída misturada, portanto, não use -u.

Se você quiser manter o pedido (portanto, toda a saída do handle_1 é anterior ao handle_2 e, portanto, você pode evitar a classificação):

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

Se você ainda quiser classificá-lo, é possível fazer o paralelismo da classificação e utilizar sort -m :

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

Defina $ TMPDIR como um diretório que seja grande o suficiente para conter a saída.

    
por 25.10.2012 / 14:15
2

Talvez eu esteja sentindo falta de algo, mas você não pode fazer isso:

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

Se você quiser que as linhas de cada processo não sejam intercaladas, é mais fácil certificar-se de que o próprio processo as grave completamente e, possivelmente, desabilite o buffer de saída, já que write s para um pipe tem a garantia de ser atômico. eles não são maiores que PIPE_BUF . Por exemplo, você pode ter certeza de que faz usar buffer de saída à lastdio e chamar fflush ou qualquer equivalente em python depois de uma ou algumas linhas terem sido gravadas.

Se você não pode modificar os scripts python, você pode fazer:

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

(com o GNU grep) ou:

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

(Veja notas nos comentários abaixo se a saída dos comandos não é texto)

E faça:

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

Outra opção para evitar esses processos de 3 lb é ter três canais para um comando que usa select / poll para ver de onde vem alguma saída e alimentá-la para sort com base em linha, mas é preciso um pouco de programação.

    
por 25.10.2012 / 14:31
1

A resposta da Flowbok foi a solução correta. Estranhamente, a saída do GNU parallel fica desconfigurada se for enviada diretamente para um arquivo - mas não se for para um tty.

Felizmente, script -c está disponível para imitar um tty.

Ainda existem três scripts:

#!/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

Depois, há um arquivo que encapsula a chamada para o paralelo:

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

E então eu chamo assim:

script -c ./run_parallel > output

As linhas na saída são misturadas linha por linha entre a saída dos diferentes scripts, mas elas não são confusas ou intercaladas em uma determinada linha.

Comportamento bizarro de parallel - posso enviar um relatório de bug.

    
por 26.10.2012 / 00:28