web-dev-qa-db-ja.com

Bash FORループを並列化する

GNU Parallelを使用して、次のスクリプト、特に3つのFORループインスタンスのそれぞれを並列化しようとしましたが、並列化できませんでした。FORループに含まれる4つのコマンドは直列に実行されます、各ループには約10分かかります。

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

単に(別名バックグラウンドで)フォークしてみませんか?

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

それが明確でない場合、重要な部分はここにあります:

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

バックグラウンドで分岐したシェルで関数を実行させる。それは平行です。

110
goldilocks

サンプルタスク

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

順次実行

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

並列実行

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

Nプロセスバッチでの並列実行

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

FIFOをセマフォとして使用して、新しいプロセスができるだけ早く生成され、同時に実行されるプロセスがN個を超えないようにすることもできます。しかし、それはより多くのコードを必要とします。

FIFOベースのセマフォを持つNプロセス:

# 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
    (
     ( "$@"; )
    # 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 

説明:

(= printf)トークンをプッシュして(= read)トークンをポップすることにより、ファイル記述子3をセマフォとして使用します('000')。実行されたタスクの戻りコードをプッシュすることで、何か問題が発生した場合に中止できます。

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

実際に機能するかどうかは、コマンドによって異なります。私はそれらに精通していません。 rm *.mat並列実行すると、競合が発生しやすくなります...

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

これはセマフォを使用し、利用可能なコアの数と同じ数の反復を並列化します(-j +0は、並列化することを意味しますN + 0ジョブ、ここでNは使用可能なコアの数です)。

sem --waitは、forループのすべての反復が実行を終了するまで待機してから、コードの連続する行を実行します。

注: GNU parallel project (Sudo apt-get install parallel)から「並列」が必要になります。

30
lev

私が頻繁に使用する本当に簡単な方法の1つ:

cat "args" | xargs -P $NUM_PARALLEL command

これにより、「args」ファイルの各行を並列に渡してコマンドが実行され、最大で$ NUM_PARALLELが同時に実行されます。

別の場所で入力引数を置き換える必要がある場合は、xargsの-Iオプションを調べることもできます。

11
eyeApps LLC

Fslジョブは相互に依存しているようです。そのため、4つのジョブを並行して実行することはできません。ただし、実行は並行して実行できます。

単一の実行を実行するbash関数を作成し、その関数を並行して実行します。

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

詳細については、紹介ビデオをご覧ください: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1 そして、チュートリアルを1時間歩いてください http://www.gnu.org/software/parallel/parallel_tutorial.html コマンドラインはあなたを愛しています。

7
Ole Tange

最大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"
7

@levの回答がとても気に入っています。非常にシンプルな方法でプロセスの最大数を制御できるからです。ただし、 manual で説明されているように、semは角括弧では機能しません。

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

仕事をします。

-j + N CPUコアの数にNを追加します。この数までのジョブを並行して実行します。計算集中型のジョブの場合、-j +0は、number-cpu-coresジョブを同時に実行するので便利です。

-j -N CPUコアの数からNを引きます。この数までのジョブを並行して実行します。評価された数値が1未満の場合、1が使用されます。 --use-cpus-instead-of-coresも参照してください。

5
moritzschaefer

私の場合、セマフォを使用できない(Windowsではgit-bashを使用している)ため、作業を開始する前に、N個のワーカー間でタスクを分割する一般的な方法を考え出しました。

タスクがほぼ同じ時間かかる場合、うまく機能します。不利な点は、労働者の1人がその仕事をするのに長い時間がかかる場合、すでに終了した他の労働者は役に立たないということです。

Nワーカー間でジョブを分割する(コアごとに1)

# 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

@PSkocikのソリューションで問題が発生しました。私のシステムではGNU Parallelをパッケージとして使用できず、semをビルドして手動で実行すると例外がスローされました。次に、セマフォFIFOの例も試しましたが、通信に関する他のエラーも発生しました。

@eyeAppsはxargsを提案しましたが、複雑な使用例で動作させる方法がわかりませんでした(例は大歓迎です)。

_jobs_set_max_parallelで構成された一度に最大N個のジョブを処理する並列ジョブの私の解決策を次に示します。

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

使用例:

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