Paralelizar um Bash FOR Loop

78

Eu tenho tentado paralelizar o seguinte script, especificamente cada uma das três instâncias do loop FOR, usando o GNU Parallel, mas não consegui. Os 4 comandos contidos no loop FOR são executados em série, cada loop leva cerca de 10 minutos.

#!/bin/bash

kar='KAR5'
runList='run2 run3 run4'
mkdir normFunc
for run in $runList
do 
  fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
  fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
  fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
  fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear

  rm -f *.mat
done
    
por Ravnoor S Gill 05.12.2013 / 22:04

10 respostas

69

Por que você simplesmente não bifurca (aka background) eles?

foo () {
    local run=$1
    fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}

for run in $runList; do foo "$run" & done

Caso isso não esteja claro, a parte significativa está aqui:

for run in $runList; do foo "$run" & done
                                   ^

Faz com que a função seja executada em um shell bifurcado em segundo plano. Isso é paralelo.

    
por 05.12.2013 / 22:11
101

Tarefa de amostra

task(){
   sleep 0.5; echo "$1";
}

Execuções sequenciais

for thing in a b c d e f g; do 
   task "$thing"
done

Execuções paralelas

for thing in a b c d e f g; do 
  task "$thing" &
done

Execuções paralelas em lotes de processo N

N=4
(
for thing in a b c d e f g; do 
   ((i=i%N)); ((i++==0)) && wait
   task "$thing" & 
done
)

Também é possível usar FIFOs como semáforos e usá-los para garantir que novos processos sejam gerados o mais rápido possível e que não mais de N processos sejam executados ao mesmo tempo. Mas requer mais código.

N processa com um semáforo baseado em FIFO:

open_sem(){
    mkfifo pipe-$$
    exec 3<>pipe-$$
    rm pipe-$$
    local i=$1
    for((;i>0;i--)); do
        printf %s 000 >&3
    done
}
run_with_lock(){
    local x
    read -u 3 -n 3 x && ((0==x)) || exit $x
    (
    "$@" 
    printf '%.3d' $? >&3
    )&
}

N=4
open_sem $N
for thing in {a..g}; do
    run_with_lock task $thing
done 
    
por 16.07.2015 / 16:05
56
for stuff in things
do
( something
  with
  stuff ) &
done
wait # for all the something with stuff

Se realmente funciona, depende dos seus comandos; Eu não estou familiarizado com eles. O rm *.mat parece um pouco propenso a conflitos se for executado em paralelo ...

    
por 05.12.2013 / 22:10
24
for stuff in things
do
sem -j+0 ( something
  with
  stuff )
done
sem --wait

Isso usará semáforos, paralelizando tantas iterações quanto o número de núcleos disponíveis (-j + 0 significa que você fará o paralelismo N + 0 jobs , onde N é o número de núcleos disponíveis ).

sem --wait diz para esperar até que todas as iterações no loop for tenham terminado a execução antes de executar as linhas sucessivas do código.

Nota: você precisará de "paralelo" do projeto paralelo GNU (sudo apt-get install parallel).

    
por 16.07.2015 / 15:34
7

Uma maneira muito fácil de usar:

cat "args" | xargs -P $NUM_PARALLEL command

Isso executará o comando, passando em cada linha do arquivo "args", em paralelo, executando no máximo $ NUM_PARALLEL ao mesmo tempo.

Você também pode olhar para a opção -I para xargs, se precisar substituir os argumentos de entrada em lugares diferentes.

    
por 28.01.2017 / 08:05
6

Parece que os trabalhos fsl estão dependendo um do outro, então os 4 trabalhos não podem ser executados em paralelo. As execuções, no entanto, podem ser executadas em paralelo.

Crie uma função bash executando uma única execução e execute essa função em paralelo:

#!/bin/bash

myfunc() {
    run=$1
    kar='KAR5'
    mkdir normFunc
    fsl5.0-flirt -in $kar"deformed.nii.gz" -ref normtemp.nii.gz -omat $run".norm1.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref $kar"deformed.nii.gz" -omat $run".norm2.mat" -bins 256 -cost corratio -searchrx -90 90 -searchry -90 90 -searchrz -90 90 -dof 12 
    fsl5.0-convert_xfm -concat $run".norm1.mat" -omat $run".norm.mat" $run".norm2.mat"
    fsl5.0-flirt -in $run".poststats.nii.gz" -ref normtemp.nii.gz -out $PWD/normFunc/$run".norm.nii.gz" -applyxfm -init $run".norm.mat" -interp trilinear
}

export -f myfunc
parallel myfunc ::: run2 run3 run4

Para saber mais, assista aos vídeos de introdução: link e passe uma hora caminhando no tutorial link Sua linha de comando vai adorar você por isso.

    
por 06.12.2013 / 11:16
2

Execução paralela no máximo N-process concorrentes

#!/bin/bash

N=4

for i in {a..z}; do
    (
        # .. do your stuff here
        echo "starting task $i.."
        sleep $(( (RANDOM % 3) + 1))
    ) &

    # allow only to execute $N jobs in parallel
    if [[ $(jobs -r -p | wc -l) -gt $N ]]; then
        # wait only for first job
        wait -n
    fi

done

# wait for pending jobs
wait

echo "all done"
    
por 10.04.2018 / 10:04
0

Eu tive problemas com a solução @PSkocik . Meu sistema não tem GNU Parallel disponível como um pacote e sem lançou uma exceção quando eu construí e executei manualmente. Eu então tentei o exemplo de semáforo FIFO também, o que também gerou alguns outros erros em relação à comunicação.

@eyeApps sugeriu xargs, mas eu não sabia como fazer isso funcionar com meu caso de uso complexo (exemplos seriam bem-vindos).

Aqui está minha solução para trabalhos paralelos que processam até N jobs por vez, conforme configurado por _jobs_set_max_parallel :

_lib_jobs.sh:

function _jobs_get_count_e {
   jobs -r | wc -l | tr -d " "
}

function _jobs_set_max_parallel {
   g_jobs_max_jobs=$1
}

function _jobs_get_max_parallel_e {
   [[ $g_jobs_max_jobs ]] && {
      echo $g_jobs_max_jobs

      echo 0
   }

   echo 1
}

function _jobs_is_parallel_available_r() {
   (( $(_jobs_get_count_e) < $g_jobs_max_jobs )) &&
      return 0

   return 1
}

function _jobs_wait_parallel() {
   # Sleep between available jobs
   while true; do
      _jobs_is_parallel_available_r &&
         break

      sleep 0.1s
   done
}

function _jobs_wait() {
   wait
}

Exemplo de uso:

#!/bin/bash

source "_lib_jobs.sh"

_jobs_set_max_parallel 3

# Run 10 jobs in parallel with varying amounts of work
for a in {1..10}; do
   _jobs_wait_parallel

   # Sleep between 1-2 seconds to simulate busy work
   sleep_delay=$(echo "scale=1; $(shuf -i 10-20 -n 1)/10" | bc -l)

   ( ### ASYNC
   echo $a
   sleep ${sleep_delay}s
   ) &
done

# Visualize jobs
while true; do
   n_jobs=$(_jobs_get_count_e)

   [[ $n_jobs = 0 ]] &&
      break

   sleep 0.1s
done
    
por 16.03.2017 / 23:35
0

No meu caso, não consigo usar semáforo (estou no git-bash no Windows), então criei uma maneira genérica de dividir a tarefa entre os N funcionários, antes de começarem.

Funciona bem se as tarefas demorarem aproximadamente a mesma quantidade de tempo. A desvantagem é que, se um dos trabalhadores demorar muito para fazer sua parte do trabalho, os outros que já terminaram não ajudarão.

Dividindo o trabalho entre N trabalhadores (1 por núcleo)

# array of assets, assuming at least 1 item exists
listAssets=( {a..z} ) # example: a b c d .. z
# listAssets=( ~/"path with spaces/"*.txt ) # could be file paths

# replace with your task
task() { # $1 = idWorker, $2 = asset
  echo "Worker $1: Asset '$2' START!"
  # simulating a task that randomly takes 3-6 seconds
  sleep $(( ($RANDOM % 4) + 3 ))
  echo "    Worker $1: Asset '$2' OK!"
}

nVirtualCores=$(nproc --all)
nWorkers=$(( $nVirtualCores * 1 )) # I want 1 process per core

worker() { # $1 = idWorker
  echo "Worker $1 GO!"
  idAsset=0
  for asset in "${listAssets[@]}"; do
    # split assets among workers (using modulo); each worker will go through
    # the list and select the asset only if it belongs to that worker
    (( idAsset % nWorkers == $1 )) && task $1 "$asset"
    (( idAsset++ ))
  done
  echo "    Worker $1 ALL DONE!"
}

for (( idWorker=0; idWorker<nWorkers; idWorker++ )); do
  # start workers in parallel, use 1 process for each
  worker $idWorker &
done
wait # until all workers are done
    
por 26.05.2018 / 21:14
0

Eu realmente gosto da resposta do @lev, pois fornece controle sobre o número máximo de processos de uma maneira muito simples. No entanto, conforme descrito no manual , o sem não funciona com colchetes.

for stuff in things
do
sem -j +0 "something; \
  with; \
  stuff"
done
sem --wait

faz o trabalho.

-j +N Add N to the number of CPU cores. Run up to this many jobs in parallel. For compute intensive jobs -j +0 is useful as it will run number-of-cpu-cores jobs simultaneously.

-j -N Subtract N from the number of CPU cores. Run up to this many jobs in parallel. If the evaluated number is less than 1 then 1 will be used. See also --use-cpus-instead-of-cores.

    
por 11.10.2018 / 13:58