Loops de casca paralela

11

Quero processar muitos arquivos e, como aqui tenho vários núcleos, quero fazê-lo em paralelo:

for i in *.myfiles; do do_something $i `derived_params $i` other_params; done

Conheço uma solução Makefile , mas meus comandos precisam dos argumentos da lista de globbing do shell. O que eu encontrei é:

> function pwait() {
>     while [ $(jobs -p | wc -l) -ge $1 ]; do
>         sleep 1
>     done
> }
>

Para usá-lo, basta colocar e após os trabalhos e uma chamada em espera, o parâmetro fornece o número de processos paralelos:

> for i in *; do
>     do_something $i &
>     pwait 10
> done

Mas isso não funciona muito bem, por exemplo, eu tentei com, por exemplo, um loop for convertendo muitos arquivos, mas com erros e deixando os trabalhos desfeitos.

Não posso acreditar que isso ainda não tenha sido feito, já que a discussão na lista de discussão do zsh já é antiga. Então você conhece melhor?

matemática
fonte
Semelhante a esta pergunta: superuser.com/questions/153630/… Veja se essa técnica funciona para você.
JRobert
Seria útil se você publicasse as mensagens de erro.
Pausado até novo aviso.
@JRobert sim, eu sabia disso, mas isso realmente não ajuda, pois a abordagem do makefile não funcionará como eu disse! @ Dennis: Ok, primeiro deixo executar um top ao lado, mostrando-me mais do que o número especificado de processos. Segundo, ele não retorna ao prompt corretamente. Terceiro, eu disse que deixa os trabalhos desfeitos não estava certo: acabei de colocar um indicador echo "DONE"após o loop, que era executado antes dos trabalhos ativos não serem concluídos. => Isso me fez pensar que os trabalhos não foram feitos.
math

Respostas:

15

Um makefile é uma boa solução para o seu problema. Você pode programar essa execução paralela em um shell, mas é difícil, como você notou. Uma implementação paralela do make não apenas cuida do início dos trabalhos e detecta sua terminação, mas também lida com o balanceamento de carga, o que é complicado.

O requisito de globbing não é um obstáculo: existem implementações que o apóiam. GNU make, que possui expansão de curinga como $(wildcard *.c)e acesso ao shell como $(shell mycommand)(funções de pesquisa no GNU make manual para mais informações). É o padrão makeno Linux e está disponível na maioria dos outros sistemas. Aqui está um esqueleto do Makefile que você pode adaptar às suas necessidades:

fontes = $ (curinga * .src)

all: $ (fontes: .src = .tgt)

% .tgt: $ .src
    do_something $ <$$ (derivado_params $ <)> $ @

Execute algo como make -j4executar quatro tarefas em paralelo ou make -j -l3manter a carga média em torno de 3.

Gilles 'SO- parar de ser mau'
fonte
8

Não tenho certeza de como são seus argumentos derivados. Mas com o GNU Parallel http: // www.gnu.org/software/parallel/, você pode fazer isso para executar um trabalho por núcleo de CPU:

find . | parallel -j+0 'a={}; name=${a##*/}; upper=$(echo "$name" | tr "[:lower:]" "[:upper:]");
   echo "$name - $upper"'

Se o que você deseja derivar é simplesmente alterar a extensão. {{}} Pode ser útil:

parallel -j+0 lame {} -o {.}.mp3 ::: *.wav

Assista ao vídeo de introdução ao GNU Parallel em http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
fonte
7

Usar o waitcomando do shell não funcionaria para você?

for i in *
do
    do_something $i &
done
wait

Seu loop executa um trabalho, espera por ele e executa o próximo trabalho. Se o acima não funcionar para você, o seu poderá funcionar melhor se você seguir pwaitem frente done.

Pausado até novo aviso.
fonte
não com 1 milhão de arquivos, eu teria 1 milhão de processos em execução ou estou errado?
math
1
@brubelsabs: Bem, ele tentaria fazer um milhão de processos. Você não disse em sua pergunta quantos arquivos precisava processar. Eu acho que você precisaria usar forloops aninhados para limitar isso: for file in *; do for i in {1..10}; do do_something "$i" & done; wait; done(não testado) Isso deve fazer dez de cada vez e esperar até que todos os dez de cada grupo estejam concluídos antes de iniciar os próximos dez. Seu loop faz um de cada vez, &discutindo. Veja a pergunta à qual o JRobert se vinculou para outras opções. Procure no Stack Overflow outras perguntas semelhantes às suas (e essa).
Pausado até novo aviso.
Se o OP antecipar um milhão de arquivos, ele terá um problema for i in *. Ele teria que passar argumentos para o circuito com um cano ou algo assim. Então, em vez de um loop interno, você pode executar um contador de incremento e executar "micro-"wait"-s"cada "$ ((i% 32))" -eq '0'
@DennisWilliamson: combinar waitcom um loop interno do contador funcionou bem para mim. Obrigado!
Joel Purra
3

Por que ninguém mencionou xargs ainda?

Supondo que você tenha exatamente três argumentos,

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; done | xargs -n 3 -P $PROCS do_something

Caso contrário, use um delimitador (nulo é útil para isso):

for i in *.myfiles; do echo -n $i `derived_params $i` other_params; echo -ne "\0"; done | xargs -0 -n 1 -P $PROCS do_something

EDIT: para o exposto, cada parâmetro deve ser separado por um caractere nulo e, em seguida, o número de parâmetros deve ser especificado com o xargs -n.

zebediah49
fonte
Sim, em nosso projeto, alguém teve a mesma ideia e funciona muito bem mesmo no Windows com MSys.
math
0

Eu tentei algumas das respostas. Eles tornam o script um pouco mais complexo do que o necessário. O ideal é usar parallelou xargsseria preferível, no entanto, se as operações dentro do loop for forem complicadas, pode ser problemático criar arquivos de linhas grandes e longas para fornecer em paralelo. em vez disso, poderíamos usar a fonte da seguinte forma

# Create a test file 
$ cat test.txt
task_test 1
task_test 2

# Create a shell source file 
$ cat task.sh
task_test()
{
    echo $1
}

# use the source under bash -c 
$ cat test.txt | xargs -n1 -I{} bash -c 'source task.sh; {}'
1
2

Assim, para a sua solução de problemas, seria

for i in *.myfiles; echo " do_something $i `derived_params $i` other_params
" >> commands.txt ; done

definir fazer algo como do_something.sh

do_something(){
process $1
echo $2 
whatever $3 

}

executar com xargougnu parallel

   cat commands.txt | xargs -n1 -I{} -P8 bash -c 'source do_something.sh; {}'

Presumo que a independência funcional das iterações de for esteja implícita.

vegabondx
fonte