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
単に(別名バックグラウンドで)フォークしてみませんか?
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
^
バックグラウンドで分岐したシェルで関数を実行させる。それは平行です。
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=4
(
for thing in a b c d e f g; do
((i=i%N)); ((i++==0)) && wait
task "$thing" &
done
)
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'
)。実行されたタスクの戻りコードをプッシュすることで、何か問題が発生した場合に中止できます。
for stuff in things
do
( something
with
stuff ) &
done
wait # for all the something with stuff
実際に機能するかどうかは、コマンドによって異なります。私はそれらに精通していません。 rm *.mat
並列実行すると、競合が発生しやすくなります...
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)から「並列」が必要になります。
私が頻繁に使用する本当に簡単な方法の1つ:
cat "args" | xargs -P $NUM_PARALLEL command
これにより、「args」ファイルの各行を並列に渡してコマンドが実行され、最大で$ NUM_PARALLELが同時に実行されます。
別の場所で入力引数を置き換える必要がある場合は、xargsの-Iオプションを調べることもできます。
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 コマンドラインはあなたを愛しています。
#!/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"
@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も参照してください。
私の場合、セマフォを使用できない(Windowsではgit-bashを使用している)ため、作業を開始する前に、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
@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