Równoległa pętla Bash FOR


109

Próbowałem zrównoleglić następujący skrypt, a konkretnie każdą z trzech instancji pętli FOR, używając GNU Parallel, ale nie byłem w stanie. 4 polecenia zawarte w pętli FOR działają szeregowo, każda pętla zajmuje około 10 minut.

#!/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

Odpowiedzi:


94

Dlaczego ich nie rozwidlisz (aka. Tła)?

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

W przypadku, gdy nie jest to jasne, znacząca część jest tutaj:

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

Powoduje wykonanie funkcji w rozwidlonej powłoce w tle. To równolegle.


6
To działało jak urok. Dziękuję Ci. Taka prosta implementacja (Sprawia, że ​​czuję się teraz tak głupio!).
Ravnoor S Gill,

8
W przypadku gdy miałem 8 plików do równoległego działania, ale tylko 4 rdzenie, czy można to zintegrować z takim ustawieniem, czy wymagałoby to Harmonogramu zadań?
Ravnoor S Gill,

6
W tym kontekście tak naprawdę nie ma to znaczenia; normalne jest, że system ma bardziej aktywne procesy niż rdzenie. Jeśli masz wiele krótkich zadań , najlepiej byłoby zasilić kolejkę obsługiwaną przez liczbę lub wątki robocze <liczbę rdzeni. Nie wiem, jak często tak się dzieje w skryptach powłoki (w takim przypadku nie byłyby to wątki, byłyby niezależnymi procesami), ale przy stosunkowo niewielu długich zadaniach byłoby to bezcelowe. Zajmie się nimi harmonogram systemu operacyjnego.
goldilocks,

17
Możesz także dodać waitpolecenie na końcu, aby skrypt główny nie zakończył działania, dopóki nie zakończą się wszystkie zadania w tle.
psusi

1
Dobrze byłoby również ograniczyć liczbę równoczesnych procesów: każdy z moich procesów wykorzystuje 100% czasu rdzenia przez około 25 minut. Jest to na wspólnym serwerze z 16 rdzeniami, na którym wiele osób wykonuje zadania. Muszę uruchomić 23 kopie skryptu. Jeśli uruchomię je wszystkie jednocześnie, wówczas zalewam serwer i robię go bezużytecznym dla innych przez godzinę lub dwie (obciążenie wzrasta do 30, wszystko inne spowalnia znacznie). Myślę, że da się to zrobić nice, ale nie wiem, czy to się kiedykolwiek skończy ...
naught101

150

Przykładowe zadanie

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

Przebiegi sekwencyjne

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

Równoległe przebiegi

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

Równoległe przebiegi w partiach N-procesowych

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

Możliwe jest również użycie FIFO jako semaforów i użycie ich, aby zapewnić, że nowe procesy zostaną odrodzone tak szybko, jak to możliwe, i że nie więcej niż N procesów uruchomi się w tym samym czasie. Ale wymaga więcej kodu.

N procesów z semaforem opartym na 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 

4
Linia z waitnim w zasadzie pozwala na uruchomienie wszystkich procesów, dopóki nie dotrze do nthprocesu, a następnie czeka, aż wszystkie pozostałe zakończą działanie, prawda?
naught101

Jeśli iwynosi zero, zadzwoń, czekaj. Przyrost ipo teście zerowym.
PSkocik

2
@ naught101 Tak. waitw / no arg czeka na wszystkie dzieci. To trochę marnuje. Podejście oparte na semaforze oparte na potoku zapewnia płynniejszą współbieżność ( -nt-ot
używałem

1
@ BeowulfNode42 Nie musisz wychodzić. Status powrotu zadania nie zaszkodzi spójności semafora, dopóki status (lub coś o tej długości) zostanie zapisany z powrotem do fifo po zakończeniu / awarii procesu zadania.
PSkocik,

1
Do Twojej wiadomości mkfifo pipe-$$polecenie wymaga odpowiedniego dostępu do zapisu w bieżącym katalogu. Wolę więc podać pełną ścieżkę, ponieważ /tmp/pipe-$$najprawdopodobniej ma ona dostęp do zapisu dla bieżącego użytkownika, zamiast polegać na jakimkolwiek bieżącym katalogu. Tak, zastąp wszystkie 3 wystąpienia pipe-$$.
BeowulfNode42

65
for stuff in things
do
( something
  with
  stuff ) &
done
wait # for all the something with stuff

To, czy rzeczywiście działa, zależy od twoich poleceń; Nie znam ich. rm *.matWygląda trochę podatne na konflikty jeśli przebiega równolegle ...


2
To również działa idealnie. Masz rację, musiałbym przejść rm *.matna coś takiego, rm $run".mat"aby uruchomić to bez zakłócania jednego procesu przez drugi proces. Dziękuję .
Ravnoor S Gill

@RavnoorSGill Witamy w Stack Exchange! Jeśli ta odpowiedź rozwiązała problem, zaznacz go jako zaakceptowany , zaznaczając obok niego znacznik wyboru.
Gilles,

7
+1 za wait, o których zapomniałem.
goldilocks,

5
Jeśli jest mnóstwo „rzeczy”, czyż to nie uruchomi ton procesów? Lepiej byłoby uruchomić jednocześnie rozsądną liczbę procesów, prawda?
David Doria,

1
Bardzo pomocna wskazówka! Jak ustawić liczbę wątków w tym przypadku?
Dadong Zhang

30
for stuff in things
do
sem -j+0 ( something
  with
  stuff )
done
sem --wait

Użyjemy semaforów, równolegle tyle iteracji, ile jest dostępnych rdzeni (-j +0 oznacza, że ​​zrównoleglycie zadania N + 0 , gdzie N jest liczbą dostępnych rdzeni ).

sem --wait każe poczekać, aż wszystkie iteracje w pętli for zakończą wykonywanie przed wykonaniem kolejnych wierszy kodu.

Uwaga: będziesz potrzebować „równoległego” z równoległego projektu GNU (sudo apt-get install parallel).


1
czy można przekroczyć 60 lat? mine zgłasza błąd informujący o niewystarczającej liczbie deskryptorów plików.
chovy

Jeśli powoduje to błąd składniowy z powodu nawiasów klamrowych również dla każdego, spójrz na odpowiedź moritzschaefer.
Nicolai

10

Jeden naprawdę łatwy sposób, którego często używam:

cat "args" | xargs -P $NUM_PARALLEL command

Spowoduje to uruchomienie polecenia, przekazującego równolegle do każdej linii pliku „args”, co najwyżej $ NUM_PARALLEL w tym samym czasie.

Możesz także spojrzeć na opcję -I dla xargs, jeśli chcesz zastąpić argumenty wejściowe w różnych miejscach.


6

Wygląda na to, że zadania fsl zależą od siebie, więc 4 zadania nie mogą być uruchamiane równolegle. Przebiegi można jednak uruchamiać równolegle.

Uczyń funkcję bash uruchamiającą pojedynczy przebieg i uruchom tę funkcję równolegle:

#!/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

Aby dowiedzieć się więcej, obejrzyj filmy wprowadzające: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 i poświęć godzinę na zapoznanie się z samouczkiem http://www.gnu.org/software/parallel/parallel_tutorial.html Twoje polecenie linia cię za to pokocha.


Jeśli używasz powłoki innej niż bash, musisz to zrobić export SHELL=/bin/bashprzed uruchomieniem równoległym. W przeciwnym razie pojawi się błąd:Unknown command 'myfunc arg'
AndrewHarvey

1
@AndrewHarvey: czy nie po to jest ten shebang?
naught101

5

Równoległe wykonywanie w maksymalnej liczbie równoległych procesów N

#!/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"

3

Naprawdę podoba mi się odpowiedź @lev, ponieważ zapewnia kontrolę nad maksymalną liczbą procesów w bardzo prosty sposób. Jednak jak opisano w instrukcji , sem nie działa z nawiasami.

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

Wykonuje pracę.

-j + N Dodaj N do liczby rdzeni procesora. Podejdź do tak wielu zadań równolegle. Przy intensywnych zadaniach obliczeniowych przydatna jest opcja -j +0, ponieważ będzie ona jednocześnie wykonywać zadania z liczbą rdzeni procesora.

-j -N Odejmij N od liczby rdzeni procesora. Podejdź do tak wielu zadań równolegle. Jeśli oszacowana liczba jest mniejsza niż 1, zostanie użyta 1. Zobacz także --use-cpus-zamiast-rdzeni.


1

W moim przypadku nie mogę używać semaforów (jestem w git-bash na Windowsie), więc wymyśliłem ogólny sposób podziału zadania na N pracowników, zanim zaczną.

Działa dobrze, jeśli zadania zajmują mniej więcej tyle samo czasu. Wadą jest to, że jeśli jeden z pracowników zajmuje dużo czasu na wykonanie swojej części pracy, inni, którzy już skończyli, nie pomogą.

Podział pracy na N pracowników (1 na rdzeń)

# 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

0

Miałem problem z @PSkocikrozwiązaniem. Mój system nie ma GNU Parallel dostępnego jako pakiet i semzgłosił wyjątek, kiedy go zbudowałem i uruchomiłem ręcznie. Następnie spróbowałem również przykładu semafora FIFO, który również rzucił kilka innych błędów dotyczących komunikacji.

@eyeApps zasugerowałem xargs, ale nie wiedziałem, jak sprawić, by działał z moim złożonym przypadkiem użycia (przykłady byłyby mile widziane).

Oto moje rozwiązanie dla zadań równoległych przetwarzających do Nzadań jednocześnie skonfigurowanych przez _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
}

Przykładowe użycie:

#!/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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.