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

10

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

Esse script Python é lento e vinculado à CPU (para um único núcleo em uma máquina com vários núcleos), então eu quero executar três instâncias dele - uma para cada tipo de dados - e combinar sua saída para transmiti-la sort. Basicamente, equivalente a isso:

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

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

Eu encontrei essa pergunta em que o GNU splitestava sendo usado para alternar entre o fluxo stdout e as instâncias de um script que lida com o fluxo.

Na página do manual 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

Portanto, o r/Ncomando implica " sem dividir linhas ".

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

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

Onde choose_scripté que isto:

#!/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 simples do bash 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 de manual que colei acima, ele deve manter a integridade da linha.

Obviamente funcionará se eu remover o -uargumento, mas ele será armazenado em buffer e a memória ficará insuficiente, pois armazena em buffer a saída de todos os scripts, exceto um.

Se alguém tiver alguma ideia aqui, seria muito apreciada. Estou fora da minha profundidade aqui.

Cera
fonte
Algumas pessoas no #bash no freenode sugeriram que eu gerasse todos os três processos e os background, escrevendo em FDs personalizados, depois passando por cima desses FDs e lendo as linhas para eles, mas não descobri como tornar isso viável. Também me disseram para olhar para o coprocbuilt-in no bash, embora eu realmente não veja como ele se aplica.
Cera
11
Você precisa fazer isso sem arquivos intermediários? Você não poderia simplesmente fazer job1.py > file1 & job2.py > file 2 & job3.py > file3 ; wait ; sort -n file1 file2 file3?
angus

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 em buffer todo o processo.

flowblok
fonte
Estou um pouco confuso - é o fato Xde IXdizer -Ique X será a bandeira para substituir ou está aplicando a -Xbandeira, que aparentemente também tem um significado relevante?
Cera
Hmph. Estou fazendo o seguinte parallel -u -X ./handle_{}.sh ::: "1" "2" "3":, e infelizmente ainda estou vendo alguma saída confusa.
Cera
o primeiro: você também pode usar parallel -u ./handle_{}.sh, mas eu prefiro alterá-lo, pois chaves também têm o significado de unir comandos (como na sua pergunta).
flowblok
Parece funcionar para mim, meu grep não pegar qualquer deturpação: pastie.org/5113187 (? Você está usando os scripts de teste Bash, ou seus scripts Python reais)
flowblok
O problema é que isso não está realmente fazendo nada em paralelo. Eu estou usando os scripts bash - pastie.org/5113225
Cera
2

Tentar:

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

Se handle_1.pyleva um nome de arquivo:

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

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

Se você deseja manter o pedido (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 deseja classificá-lo, pode paralelizar a 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 grande o suficiente para armazenar a saída.

Ole Tange
fonte
11
Eu quero a saída 'mista' - só quero ter certeza de que cada linha na saída final seja uma única linha de um dos subprocessos. Se eu não misturar, o sistema ficará sem memória armazenando em buffer os fluxos stdout que ainda não estão sendo impressos.
Cera
Com o GNU Parallel, você não ficará sem memória: ele não faz buffer na memória. Por que você acha que ele armazena em memória?
Toll Ole
2

Talvez esteja faltando alguma coisa, mas você não pode simplesmente fazer:

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

Se você deseja que as linhas de cada processo não sejam intercaladas, o mais fácil é provavelmente garantir que o próprio processo as escreva totalmente e possivelmente desative o buffer de saída, pois writes em um pipe são garantidos como atômicos, desde que não sejam maiores que PIPE_BUF. Por exemplo, você pode ter certeza de que ele usa o buffer de saída à la stdioe call fflushou qualquer que seja o equivalente pythonapós uma ou poucas linhas serem gravadas.

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

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

(com GNU grep) ou:

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

(Veja as notas nos comentários abaixo se o que os comandos emitem não é texto)

E fazer:

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

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

Stéphane Chazelas
fonte
Você precisa de um waitlá, eu acho.
Derobert 25/10/12
11
A menos que alguns dos programas fechem seu stdout antes de sair, porque o pipe e sort -npermanecerá até que todos os programas com um fd aberto tenham saído.
Stéphane Chazelas
Na verdade, eu testei, você está correto.
Derobert 25/10/12
Não, ainda recebo uma saída mutilada. As linhas são misturadas e intercaladas.
Cera
11
OK @Cerales, veja minha resposta atualizada
Stéphane Chazelas
1

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

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

Ainda existem os 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 em 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 a linha entre a saída dos diferentes scripts, mas não são mutiladas ou intercaladas em uma determinada linha.

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

Cera
fonte