it-swarm.com.de

Parallelisieren Sie eine Bash FOR-Schleife

Ich habe versucht, das folgende Skript, insbesondere jede der drei FOR-Schleifeninstanzen, mit GNU Parallel) zu parallelisieren, konnte dies jedoch nicht. Die 4 in der FOR-Schleife enthaltenen Befehle werden in Reihe ausgeführt Jede Schleife dauert ungefähr 10 Minuten.

#!/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
128
Ravnoor S Gill

Warum gabelst du sie nicht einfach (auch bekannt als Hintergrund)?

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

Falls das nicht klar ist, ist der wesentliche Teil hier:

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

Bewirken, dass die Funktion in einer gegabelten Shell im Hintergrund ausgeführt wird. Das ist parallel.

110
goldilocks

Beispielaufgabe

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

Sequentielle Läufe

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

Parallele Läufe

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

Parallele Läufe in N-Prozess-Chargen

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

Es ist auch möglich, FIFOs als Semaphore zu verwenden und sicherzustellen, dass neue Prozesse so schnell wie möglich erzeugt werden und nicht mehr als N Prozesse gleichzeitig ausgeführt werden. Es erfordert jedoch mehr Code.

N Prozesse mit einem FIFO-basierten Semaphor:

# initialize a semaphore with a given number of tokens
open_sem(){
    mkfifo pipe-$$
    exec 3<>pipe-$$
    rm pipe-$$
    local i=$1
    for((;i>0;i--)); do
        printf %s 000 >&3
    done
}

# run the given command asynchronously and pop/Push tokens
run_with_lock(){
    local x
    # this read waits until there is something to read
    read -u 3 -n 3 x && ((0==x)) || exit $x
    (
     ( "[email protected]"; )
    # Push the return code of the command to the semaphore
    printf '%.3d' $? >&3
    )&
}

N=4
open_sem $N
for thing in {a..g}; do
    run_with_lock task $thing
done 

Erläuterung:

Wir verwenden den Dateideskriptor 3 als Semaphor, indem wir (= printf) drücken und (= read) Token ('000'). Durch Drücken des Rückkehrcodes der ausgeführten Aufgaben können wir den Vorgang abbrechen, wenn ein Fehler aufgetreten ist.

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

Ob es tatsächlich funktioniert, hängt von Ihren Befehlen ab. Ich kenne sie nicht. Das rm *.mat sieht ein bisschen konfliktanfällig aus, wenn es parallel läuft ...

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

Hierbei werden Semaphoren verwendet, die so viele Iterationen parallelisieren wie die Anzahl der verfügbaren Kerne (-j +0 bedeutet, dass Sie N + 0 Jobs parallelisieren, wobei N ist die Anzahl der verfügbaren Kerne ).

sem --wait weist an, zu warten, bis alle Iterationen in der for-Schleife die Ausführung beendet haben, bevor die aufeinanderfolgenden Codezeilen ausgeführt werden.

Hinweis: Sie benötigen "parallel" aus dem GNU paralleles Projekt (Sudo apt-get install parallel).

30
lev

Ein wirklich einfacher Weg, den ich oft benutze:

cat "args" | xargs -P $NUM_PARALLEL command

Dadurch wird der Befehl ausgeführt, der in jeder Zeile der Datei "args" parallel übergeben wird und höchstens $ NUM_PARALLEL gleichzeitig ausführt.

Sie können auch die Option -I für xargs prüfen, wenn Sie die Eingabeargumente an verschiedenen Stellen ersetzen müssen.

11
eyeApps LLC

Es scheint, dass die fsl-Jobs voneinander abhängig sind, sodass die 4 Jobs nicht parallel ausgeführt werden können. Die Läufe können jedoch parallel ausgeführt werden.

Erstellen Sie eine Bash-Funktion, die einen einzelnen Lauf ausführt, und führen Sie diese Funktion parallel aus:

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

Weitere Informationen finden Sie in den Intro-Videos: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 und gehen Sie eine Stunde lang durch das Tutorial http: //www.gnu. org/software/parallel/parallel_tutorial.html Ihre Befehlszeile wird Sie dafür lieben.

7
Ole Tange

Parallele Ausführung im max. N-Prozess gleichzeitig

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

Die Antwort von @lev gefällt mir sehr gut, da sie auf sehr einfache Weise die Kontrolle über die maximale Anzahl von Prozessen bietet. Wie im Handbuch beschrieben, funktioniert sem jedoch nicht mit Klammern.

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

Macht den Job.

-j + N Addiere N zur Anzahl der CPU-Kerne. Führen Sie so viele Jobs parallel aus. Für rechenintensive Jobs ist -j +0 nützlich, da gleichzeitig Jobs mit mehreren CPU-Kernen ausgeführt werden.

-j -N Subtrahiere N von der Anzahl der CPU-Kerne. Führen Sie so viele Jobs parallel aus. Wenn die ausgewertete Zahl kleiner als 1 ist, wird 1 verwendet. Siehe auch --use-cpus-anstelle-von-Kernen.

5
moritzschaefer

In meinem Fall kann ich kein Semaphor verwenden (ich bin unter Windows in Git-Bash), daher habe ich mir eine generische Methode ausgedacht, um die Aufgabe unter N Arbeitern aufzuteilen, bevor sie beginnen.

Es funktioniert gut, wenn die Aufgaben ungefähr genauso lange dauern. Der Nachteil ist, dass, wenn einer der Arbeiter lange braucht, um seinen Teil der Arbeit zu erledigen, die anderen, die bereits fertig sind, nicht helfen.

Aufteilung des Jobs auf N Arbeiter (1 pro Kern)

# 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
2
geekley

Ich hatte Probleme mit der Lösung von @PSkocik. Auf meinem System ist GNU Parallel nicht als Paket verfügbar, und sem hat beim manuellen Erstellen und Ausführen eine Ausnahme ausgelöst. Ich habe dann auch das Semaphor-Beispiel FIFO ausprobiert, das auch einige andere Kommunikationsfehler verursachte.

@eyeApps Schlug xargs vor, aber ich wusste nicht, wie ich es mit meinem komplexen Anwendungsfall zum Laufen bringen sollte (Beispiele wären willkommen).

Hier ist meine Lösung für parallele Jobs, die bis zu N Jobs gleichzeitig verarbeiten, wie durch _jobs_set_max_parallel Konfiguriert:

_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
}

Anwendungsbeispiel:

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