Executando comandos canalizados em paralelo

14

Considere o seguinte cenário. Eu tenho dois programas A e B. Programa A saídas para linhas stdout de seqüências de caracteres enquanto o programa B processa linhas de stdin. A maneira de usar esses dois programas é claro:

foo@bar:~$ A | B

Agora notei que isso consome apenas um núcleo; por isso estou me perguntando:

Are programs A and B sharing the same computational resources? If so, is there a way to run A and B concurrently?

Outra coisa que notei é que A é executado muito mais rápido que B, por isso estou imaginando se poderia de alguma forma executar mais programas B e deixá-los processar as linhas que A produz em paralelo.

Ou seja, A produziria suas linhas, e haveria N instâncias de programas B que leriam essas linhas (quem as ler primeiro) as processaria e as produziria no stdout.

Então, minha pergunta final é:

Is there a way to pipe the output to A among several B processes without having to take care of race conditions and other inconsistencies that could potentially arise?

    
por Jernej 15.06.2013 / 10:55

2 respostas

14

Um problema com split --filter é que a saída pode ser confundida, então você obtém meia linha do processo 1 seguida por meia linha do processo 2.

GNU Parallel garante que não haverá confusão.

Então, suponha que você queira fazer:

 A | B | C

Mas esse B é terrivelmente lento e você quer paralelizar isso. Então você pode fazer:

A | parallel --pipe B | C

GNU Parallel por padrão divide em \ n e um tamanho de bloco de 1 MB. Isso pode ser ajustado com --recend e --block.

Você pode encontrar mais sobre o GNU Parallel em: link

Você pode instalar o GNU Parallel em apenas 10 segundos com:

wget -O - pi.dk/3 | sh 

Assista ao vídeo de introdução no link

    
por 15.06.2013 / 14:27
12

Quando você escreve A | B , ambos os processos são executados em paralelo. Se você os vir usando apenas um núcleo, isso provavelmente se deve às configurações de afinidade da CPU (talvez haja alguma ferramenta para gerar um processo com afinidade diferente) ou porque um processo não é suficiente para manter um núcleo inteiro e o sistema " prefere "não espalhar a computação.

Para executar vários B's com um A, você precisa de uma ferramenta como split com a opção --filter :

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

Isso, no entanto, pode estragar a ordem das linhas na saída, porque as tarefas B não estarão rodando todas na mesma velocidade. Se isso for um problema, talvez seja necessário redirecionar a saída B i-th para um arquivo intermediário e juntá-los no final usando cat . Isso, por sua vez, pode exigir um espaço considerável em disco.

Existem outras opções (por exemplo, você poderia limitar cada instância de B a uma única saída com buffer de linha, esperar até que uma "rodada" inteira de B tenha terminado, executar o equivalente a reduzir a% o mapa dosplit, e cat a saída temporária em conjunto), com vários níveis de eficiência. A opção 'round' que acabamos de descrever, por exemplo, irá aguardar a conclusão da instância mais lenta de B , de modo que será muito dependente do buffering disponível para B; [m]buffer pode ajudar, ou talvez não, dependendo de quais são as operações.

Exemplos

Gere os primeiros 1000 números e conte as linhas em paralelo:

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

Se fôssemos "marcar" as linhas, veríamos que cada primeira linha é enviada para processar # 1, cada quinta linha para processar # 5 e assim por diante. Além disso, no tempo que leva split para gerar o segundo processo, o primeiro já é um bom caminho para sua cota:

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

Ao executar em uma máquina de 2 núcleos, os processos seq , split e wc compartilham os núcleos; mas olhando mais de perto, o sistema deixa os dois primeiros processos na CPU0 e divide a CPU1 entre os processos de trabalho:

%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 split está comendo uma quantidade considerável de CPU. Isso diminuirá proporcionalmente às necessidades de A; ou seja, se A for um processo mais pesado que seq , a sobrecarga relativa de split diminuirá. Mas se A é um processo muito leve e B é bastante rápido (para que você não precise de mais do que 2-3 Bs para manter junto com A), então paralelizar com split (ou tubos em geral ) pode não valer a pena.

    
por 15.06.2013 / 12:35