Paralelismo com o xargs

Introdução

Hoje em dia é mais do que comum vermos computadores pessoais equipados com processadores multi-core. Essa característica permite um maior desempenho em determinadas tarefas por utilizar da paralelização. Porém, é muito frequente programas ainda executarem em modo sequencial (seja por necessidade ou má programação) e isso tem bastante impacto no desempenho desse programa em um ambiente multi-processado. O comando xargs tem uma característica peculiar que pode ser usada para paralelização de processos, e isso será o tema do post de hoje.

O comando xargs

Esse comando é muito utilizado em shell script e sua tarefa primordial é construir lista de parâmetros e passá-la para execução de outros comandos ou instruções [1]. Basicamente, o que ele faz é pegar cada linha da entrada, dividí-la em parâmetros baseando-se nos delimitadores “espaço” e “nova linha” e passá-los para o comando em questão. Um uso simples seria:

$ find . -name '*.pdf'
./arquivo2.pdf
./arquivo6.pdf
./arquivo3.pdf
./arquivo1.pdf
./arquivo5.pdf
./arquivo4.pdf
./arquivo7.pdf
$ find . -name '*.pdf' | xargs
./arquivo2.pdf ./arquivo6.pdf ./arquivo3.pdf ./arquivo1.pdf ./arquivo5.pdf ./arquivo4.pdf ./arquivo7.pdf

O que aconteceu foi: dado um arquivo de entrada (ex: a saída do find), o xargs pega cada linha dele e a passa como parâmetro para o comando especificado em seu parâmetro (quando não tem nada, o comando echo é usado).

O comando acima simulou a execução do comando:

$ echo ./arquivo2.pdf ./arquivo6.pdf ./arquivo3.pdf ./arquivo1.pdf ./arquivo5.pdf ./arquivo4.pdf ./arquivo7.pdf

Assim uma maneira de deletar todos os arquivos pdf do diretório atual é:

$ find . -name '*.pdf' | xargs rm -f

Você pode checar outras funcionalidades do xargs na referência [1].

Construção de parâmetros

O xargs segue algumas regras para a construção de parâmetros:

    • Por padrão, cada parâmetro é uma palavra vinda das linhas do arquivo, isso é, o xargs é orientado a palavra e não a linha [5]. Para mudar esse comportamento mude o delimitador ou use a opção -L.
    • A quantidade total de parâmetros suportada deve ser respeitada e o xargs toma todo o cuidado para que esse valor não estoure. Imagine um arquivo com 1 milhão de linhas passado para o xargs, se todas essas linhas virassem parâmetros, com certeza um erro na execução do comando aconteceria.
    • É possível limitar o número de parâmetros para o comando através da opção -n. Veja:
      $ echo p1 p2 p3 p4 | xargs
      p1 p2 p3 p4
      $ echo p1 p2 p3 p4 | xargs -n 2
      p1 p2
      p3 p4

      Para o primeiro comando envolvendo o xargs, todas as linhas do arquivo foram passadas para o echo, resultando em uma saída de uma só linha. Usando a opção -n 2, limitados o número de parâmetros para o echo em 2, isso é, a cada 2 parâmetros na entrada um echo será executado. O interessante aqui é que essas execuções são sequenciais.

  • Outro modo de se limitar os parâmetros é através da opção -L. Essa opção limita o número de linhas da entrada que formarão os parâmetros.
    $ cat arquivo.txt
    p1 p2
    p3 p4
    p5 p6
    p7 p8
    $ cat arquivo.txt | xargs -L 2
    p1 p2 p3 p4
    p5 p6 p7 p8

    O xargs, primeiramente, passa a primeira e a segunda linha para o echo. Como cada uma dessas linhas tem dois parâmetros (duas palavras) então o echo foi executado com 4 parâmetros. O mesmo ocorre na segunda execução do comando echo, porém usando a terceira e quarta linha.

  • Em geral, o xargs executa o comando sequencialmente em torno de A/B vezes sendo A o número de parâmetros na entrada e B a quantidade de parâmetros para o comando. O valor de B pode ser alterado tanto pela opção -n quanto pela -L.

A opção -P do xargs

Pela man page vemos:

$ man xargs
--max-procs=max-procs
-P max-procs
   Run up to max-procs processes at a time; the default is 1. 
   If max-procs is 0, xargs will run as many processes as possible at a time.
   Use  the  -n option  or  the -L option with -P; otherwise chances are that only one exec will be done.

A man page nos diz que toda vez que for necessário executar o comando mais de uma vez, o xargs executará max-procs processos em paralelo. Por padrão, o valor de max-procs é 1 (sem paralelização), mas pode ser mudado através da opção -P.

Um exemplo para esclarecer:

$ echo 'p1 p2 p3 p4 p5 p6 p7 p8' | xargs -n 2 -P 2

Vemos que temos 8 parâmetros de entrada e que o xargs passará 2 de cada vez para o echo, com isso, ele executará 8/2 = 4 vezes o echo. Como serão essas execuções? Sem a opção -P, as execuções seriam sequenciais, isso é, um echo é executado após o outro. Fazendo -P 2, dizemos para o xargs agrupar as 4 execuções do echo em 2 em 2 e executá-las paralelamente. Confuso?  Vamos tentar um exemplo mais numérico para esclarecer melhor:

$ time echo {1..5} | xargs -n 1 sleep
real    0m15.011s
user    0m0.001s
sys     0m0.007s
$ time echo {1..5} | xargs -n 1 -P 5 sleep
real    0m5.015s
user    0m0.000s
sys     0m0.011s

No primeiro comando, o xargs executou 5 sleep’s de forma sequencial, resultando num tempo aproximado de 15 segundos (1+2+3+4+5). Já no segundo comando, o xargs executa 5 sleep’s em paralelo. O primeiro sleep espera 1 segundo, enquanto que, ao mesmo tempo, o segundo espera 2, o terceiro 3, e assim por diante. No fim, podemos prever um tempo de execução próximo de 5 segundos que é o tempo máximo esperado.

Alguns exemplos

Muitas tarefas do dia-a-dia podem ser agilizadas se realizadas em paralelo, seja ela manipulação de vários arquivos ou compressão de vídeo/áudio. Aqui listarei somente um exemplo que mostra a vantagem de se utilizar o xargs em relação a subprocessos. Esse único exemplo serve de modelo para resolução de muitos outros problemas.

Compactação/Descompactação de vários arquivos [3]

Se você alguma vez precisou compactar vários arquivos, provavelmente você teve a ideia de fazer:

 for ARQUIVO in *.log; do gzip $ARQUIVO & ; done

Vários processos gzip’s serão executados em paralelo, como desejado. Mas qual o problema dessa utilização? O problema é que se o número de arquivos *.log for muito alto, o sistema será facilmente onerado pela quantidade absurda de processos criados. Um modo de melhorar isso é especificar um limite de execução, por exemplo 4. O novo código ficaria:

#!/bin/bash

QTD=0
for ARQUIVO in *.log
do
    [ "$QTD" = "4" ] && wait && QTD=0
    gzip $ARQUIVO &
    let QTD++
done

Esse código faz o trabalho: executa 4 processos gzip’s (provavelmente um em cada core em um processador com suporte a 4 núcleos). Porém, para executar novos processos, todos os quatro anteriores devem terminar e por isso, é provável que cores livres não sejam usados por algum tempo por causa que um processo em particular ainda não acabou sua execução. Para contornar esse problema, poderíamos montar um uma fila de processos, armazenando o PID de cada um e verificando quais processos acabaram para fazer a fila andar. Em [7] temos uma implementação desse conceito.

O xargs pode ser usado como gerenciador de “fila de processos”, em que dado um número máximo de  processos para correr em paralelo, o próprio programa manuseia a fila, liberando processos terminados e carregando os novos.

O nosso exemplo escrito usando o xargs fica:

$ ls *.log | xargs -L 1 -P 4 gzip

Para mais exemplos, veja [2], [6].

E o comando parallel?

Existe um comando de nome parallel que tem essa mesma função do xargs e está sendo adotado por alguns programadores devido a sua flexibilidade. O problema é que esse comando não vem instalado em muitas distribuições, e muitas pessoas não o instalam pelo fato do xargs suprir as suas necessidades. Não irei falar sobre esse comando aqui, mas o leitor mais curioso pode dar uma olhada no vídeo [8].

Conclusão

O comando xargs é muito útil na manipulação de parâmetros e seu uso é largamente explorado em shell scripts. Um fato interessante desse comando é que ele permite a execução paralela do comando especificado, fornecendo assim uma ótima ferramenta para aumento de desempenho de tarefas.

Referências

[1] O comando xargs, by Júlio Neves (Acessado em: Abril/2012)
http://www.dicas-l.com.br/cantinhodoshell/cantinhodoshell_20070226.php#.T3-o951mJtJ

[2] Things you (probably) didn’t know about xargs, by offbytwo (Acessado em: Abril/2012)
http://offbytwo.com/2011/06/26/things-you-didnt-know-about-xargs.html

[3] Using xargs to do parallel processing, by labrat (Acessado em: Abril/2012)
http://blog.labrat.info/20100429/using-xargs-to-do-parallel-processing/

[4] Run tasks in parallel with xargs, by http://www.linuxask.com (Acessado em: Abril/2012)
http://www.linuxask.com/questions/run-tasks-in-parallel-with-xargs

[5] xargs, by wikipedia (Acessado em: Abril/2012)
http://en.wikipedia.org/wiki/Xargs#The_separator_problem

[6] Processamento Paralelo com xargs, by João Pedro Pereira (Acessado em: Abril/2012)
http://joaopedropereira.com/blog/2010/02/22/processamento-paralelo-xargs/

[7] Elegant parallel processes in bash, by inights-into-software (Acessado em: Abril/2012)
http://insights-into-software.blogspot.com.br/2010/03/elegant-parallel-processes-in-bash.html

[8] Part 1: GNU Parallel script processing and execution, by OleTange (Acessado em: Abril/2012)
http://www.youtube.com/watch?v=OpaiGYxkSuQ

3 pensamentos sobre “Paralelismo com o xargs

  1. Pingback: Quebrando A Senha De Arquivos Zip Por Brute Force | Daemonio Labs

  2. Pingback: Quebrando A Senha De Arquivos Rar Por Brute Force | Daemonio Labs

Deixe um comentário