Executando comandos canalizados em paralelo


16

Considere o seguinte cenário. Eu tenho dois programas A e B. O programa A produz saídas padrão de linhas de strings enquanto o programa B processa linhas de stdin. A maneira de usar esses dois programas é obviamente:

foo @ bar: ~ $ A | B

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

Os programas A e B estão compartilhando os mesmos recursos computacionais? Em caso afirmativo, existe uma maneira de executar A e B simultaneamente?

Outra coisa que notei é que A roda muito mais rápido que B, portanto, estou pensando 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 dos programas B que liam essas linhas (quem as lê primeiro) as processam e as produzem no stdout.

Então, minha pergunta final é:

Existe uma maneira de canalizar a saída para A entre vários processos B sem precisar cuidar das condições da corrida e de outras inconsistências que possam surgir?


11
Embora A | B | Cseja paralelo como em processos separados, devido à natureza dos tubos (B precisa aguardar a saída de A, C deve aguardar a saída de B), ainda pode ser linear em alguns casos. Depende inteiramente do tipo de produção que eles produzem. Não há muitos casos em que executar múltiplos Bajudaria muito, é perfeitamente possível que o exemplo wc paralelo seja mais lento que o normal, wcpois a divisão pode levar mais recursos do que contar linhas normalmente. Use com cuidado.
— Frostschutz

Respostas:


14

Um problema split --filteré que a saída pode ser misturada, de modo que você obtém meia linha do processo 1, seguida por meia linha do processo 2.

O 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, portanto, você deseja paralelizar isso. Então você pode fazer:

A | parallel --pipe B | C

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

Você pode encontrar mais informações sobre o GNU Parallel em: http://www.gnu.org/s/parallel/

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

wget -O - pi.dk/3 | sh 

Assista ao vídeo de introdução em http://www.youtube.com/playlist?list=PL284C9FF2488BC6D1


11
Embora eu discorde totalmente do método de instalação :-), +1 porque sua solução resolve a maioria dos problemas com os meus.
— LSerni

Este é realmente bom. Você também tem alguma sugestão para os parâmetros a serem usados? Eu sei que o programa A produzirá mais de 1 TB de dados, aproximadamente 5 GB por minuto. O programa B processa dados 5 vezes mais devagar que A emite e tenho 5 núcleos à minha disposição para esta tarefa.
— Jernej

Atualmente, o GNU Parallel pode lidar com no máximo 100 MB / s, então você atingirá esse limite. O ideal --block-sizedependerá da quantidade de RAM e da rapidez com que você pode iniciar um novo B. Na sua situação, eu usaria --block 100Me veria como isso funcionava.
— Ole Tange

@lserni Você pode criar um método de instalação melhor, que funcione na maioria das máquinas UNIX e exija uma quantidade semelhante de trabalho do usuário?
— Ole Tange

4
Desculpe, eu não me deixei claro. O método de instalação - o script passado para sh- é ótimo. O problema está em passar para sh: baixar e executar código executável de um site . Lembre-se, talvez eu esteja sendo paranóico demais, já que alguém poderia objetar que um RPM ou DEB personalizado é basicamente a mesma coisa, e até postar o código em uma página a ser copiada e colada resultaria em pessoas fazendo isso cegamente de qualquer forma.
— LSerni

13

Quando você escreve A | B, os dois processos já são executados em paralelo. Se você vê-los usando apenas um núcleo, provavelmente é por causa das 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 armazenar 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 splita --filteropção:

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

No entanto, isso pode atrapalhar a ordem das linhas na saída, porque os trabalhos B não serão executados todos na mesma velocidade. Se este for um problema, pode ser necessário redirecionar a saída i para um arquivo intermediário e costurá-las no final usando cat. Por sua vez, isso pode exigir um espaço considerável em disco.

Existem outras opções (por exemplo, você pode limitar cada instância de B a uma única saída com buffer de linha, esperar até que toda uma "rodada" de B seja concluída, executar o equivalente a uma redução no mapa desplit s e a saída temporária em conjunto), com níveis variados de eficiência. A opção 'round' descrita acima, por exemplo, aguardará a conclusão da instância mais lenta de B , portanto, dependerá muito do buffer disponível para B; pode ajudar, ou não, dependendo de quais são as operações.cat[m]buffer

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 "marcássemos" as linhas, veríamos que cada primeira linha é enviada para o processo 1, cada quinta linha para o processo 5 e assim por diante. Além disso, no tempo necessário splitpara 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 numa máquina de dois núcleos, seq, splite os wcprocessos 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 splitestá consumindo uma quantidade considerável de CPU. Isso diminuirá proporcionalmente às necessidades de A; isto é, se A for um processo mais pesado que seq, a sobrecarga relativa splitdiminuirá. Mas se A é um processo muito leve e B é bastante rápido (para que você não precise de mais do que 2-3 B's para acompanhar A), paralelizar com split(ou tubos em geral) pode não valer a pena.


Interessante que a divisão encontrada no Ubuntu não tenha a opção --filter. Que tipo de sistema operacional está usando para isso?
— Jernej

Linux OpenSuSE 12.3, com coreutils ( gnu.org/software/coreutils/manual/html_node/… ). Vou tentar encontrar um Ubuntu, eles podem ter mudado o nome para acomodar alguma ferramenta com nome semelhante.
— LSerni

Tem certeza de que a split --filteropção está faltando? No meu Ubuntu 12.04-LTS ("wheezy / sid"), ele está lá, e meus exemplos funcionam. Você poderia ter instalado um diferente splitdo GNU coreutils?
— LSerni

Obrigado por isso. Eu tive que instalar uma versão mais recente do Coreutils. BTW, notei que se eu executar o programa A sozinho, ele consumirá um núcleo inteiro (100%) se eu executar o A | B então eles juntos comem um núcleo inteiro, processo A comendo 15% e processo B comendo 85% .. Você percebe por que isso acontece?
— Jernej

2
Isso é provável por causa do bloqueio . Se B for mais pesado que A, então A não pode enviar sua saída e fica mais lento. Outra possibilidade é A ceder a B durante sua operação (por exemplo, disco / rede). Em um sistema diferente, você pode ver B consumindo 100% da CPU1 e A sendo atribuídos 18% da CPU0. Você provavelmente precisa de 85/15 ~ 5.67 = entre 5 e 6 instâncias de B para obter uma única instância A para saturar um único núcleo. E / S, se presente, pode distorcer esses valores, no entanto.
— LSerni
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.