배쉬 FOR 루프 병렬화


109

GNU Parallel을 사용하여 다음 스크립트, 특히 세 개의 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

답변:


94

왜 그들을 포크 (일명 배경)하지 않습니까?

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
                                   ^

백그라운드에서 분기 쉘에서 함수가 실행되도록합니다. 그것은 평행입니다.


6
그것은 매력처럼 작동했습니다. 감사합니다. 그런 간단한 구현 (지금은 바보처럼 느끼게합니다!).
Ravnoor S Gill

8
병렬로 실행할 파일이 8 개인데 4 개의 코어 만있는 경우 이러한 설정에 통합 될 수 있습니까, 아니면 작업 스케줄러가 필요한가요?
Ravnoor S Gill

6
이 상황에서는 실제로 중요하지 않습니다. 시스템이 코어보다 더 활동적인 프로세스를 갖는 것은 정상입니다. 짧은 작업많은 경우 에는 코어 수보다 많거나 작업자 스레드가 서비스하는 큐를 공급하는 것이 이상적입니다. 쉘 스크립팅으로 얼마나 자주 수행되는지는 알지 못합니다 (이 경우 스레드가 아니며 독립적 인 프로세스 것입니다). 긴 작업비교적 적 으면 의미가 없습니다. OS 스케줄러가이를 관리합니다.
goldilocks

17
wait모든 백그라운드 작업이 끝날 때까지 마스터 스크립트가 종료되지 않도록 끝에 명령 을 추가 할 수도 있습니다 .
psusi

1
또한 동시 프로세스 수를 제한하는 것이 좋습니다. 프로세스마다 약 25 분 동안 코어 시간의 100 %를 사용합니다. 이것은 많은 사람들이 작업을 실행하는 16 개의 코어가있는 공유 서버에 있습니다. 스크립트 사본 23 개를 실행해야합니다. 내가 모두 동시에 실행하면 서버를 늪에 빠뜨려서 한두 시간 동안 다른 모든 사람이 쓸모 없게 만듭니다 (로드가 최대 30까지 올라가면 다른 모든 속도가 느려집니다). 나는 그것으로 할 수 있다고 생각 nice하지만, 그것이
끝날지 모르겠다

150

샘플 작업

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 프로세스 :

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
wait안에 있는 줄은 기본적으로 모든 프로세스가 프로세스에 도달 할 때까지 모든 프로세스가 실행되도록 허용 nth한 다음 다른 프로세스가 모두 완료 될 때까지 기다립니다.
naught101

경우 i제로이며, 대기 호출합니다. i제로 테스트 후 증가 합니다.
PSkocik

2
@ naught101 예. waitarg없이 모든 어린이를 기다립니다. 그것은 약간 낭비입니다. 파이프 기반-세마포어 접근 방식은 더 많은 유창 동시성을 제공합니다 (내가 사용하고있는 사용자 정의 쉘 기반 빌드 시스템에서 함께 -nt/ -ot지금 잠시 동안 성공적으로 확인)
PSkocik

1
@ BeowulfNode42 종료하지 않아도됩니다. 작업의 반환 상태는 작업의 프로세스가 종료 / 크래쉬 된 후 상태 (또는 해당 바이트 길이를 가진 것)가 fifo에 다시 쓰여지는 한 세마포의 일관성에 해를 끼치 지 않습니다.
PSkocik

1
참고로 mkfifo pipe-$$명령은 현재 디렉토리에 대한 적절한 쓰기 액세스 권한이 필요합니다. 따라서 /tmp/pipe-$$현재 디렉토리가 무엇이든 의존하지 않고 현재 사용자가 사용할 수있는 쓰기 액세스 권한 과 같은 전체 경로를 지정하는 것을 선호합니다 . 예, 3 개를 모두 대체하십시오 pipe-$$.
BeowulfNode42

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

실제로 작동하는지 여부는 명령에 따라 다릅니다. 나는 그들에게 익숙하지 않다. 는 rm *.mat이 병렬로 실행되는 경우 충돌하는 경향이 조금 보이는 ...


2
이것은 또한 완벽하게 실행됩니다. 하나의 프로세스가 다른 프로세스를 방해하지 않고 작동하도록 rm *.mat하려면 뭔가 변경 해야 할 것 rm $run".mat"입니다. 감사합니다 .
Ravnoor S Gill

@RavnoorSGill 스택 교환에 오신 것을 환영합니다! 이 답변으로 문제가 해결되면 옆에있는 확인 표시를 선택하여 승인 된 것으로 표시하십시오.
Gilles

7
+1 wait, 내가 잊어 버렸습니다.
goldilocks

5
수많은 '사물'이 있다면, 이것이 수많은 프로세스를 시작하지 않습니까? 적절한 수의 프로세스 만 동시에 시작하는 것이 좋습니다.
David Doria

1
매우 유용한 팁! 이 경우 스레드 수를 설정하는 방법은 무엇입니까?
Dadong Zhang

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

이것은 사용 가능한 코어 수만큼 반복을 병렬화하는 세마포어를 사용합니다 (-j +0은 N + 0 작업을 병렬화 함을 의미합니다 . 여기서 N은 사용 가능한 코어 수임 ).

sem --wait 는 for 루프의 모든 반복이 실행을 종료 할 때까지 기다렸다가 연속적인 코드 행을 실행하도록 지시합니다.

참고 : GNU 병렬 프로젝트 (sudo apt-get install parallel) 에서 "병렬"이 필요합니다 .


1
60을 넘을 수 있습니까? 내 파일 설명자가 충분하지 않다는 오류가 발생합니다.
chovy

누군가에게 중괄호 때문에 구문 오류가 발생하면 moritzschaefer 의 답변 을 살펴보십시오.
니콜라이

10

내가 자주 사용하는 한 가지 쉬운 방법 :

cat "args" | xargs -P $NUM_PARALLEL command

이 명령은 동시에 최대 $ NUM_PARALLEL을 실행하면서 "args"파일의 각 줄을 병렬로 전달하여 명령을 실행합니다.

다른 위치에서 입력 인수를 대체해야하는 경우 xargs의 -I 옵션을 살펴볼 수도 있습니다.


6

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을 하고 튜토리얼을 걷는 시간을 보내고 http://www.gnu.org/software/parallel/parallel_tutorial.html 당신의 명령 라인은 당신을 사랑합니다.


비 bash 쉘을 사용하는 경우 export SHELL=/bin/bash병렬을 실행하기 전에 해야합니다 . 그렇지 않으면 다음과 같은 오류가 발생합니다.Unknown command 'myfunc arg'
AndrewHarvey

1
@AndrewHarvey : 그것이 세방이 아닌가?
naught101

5

최대 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

나는 매우 간단한 방식으로 최대 프로세스 수를 제어 할 수 있기 때문에 @lev의 답변을 정말로 좋아합니다. 그러나 설명서에 설명 된대로 sem은 괄호와 함께 작동하지 않습니다.

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

일을한다.

-j + N CPU 코어 수에 N을 더합니다. 이 많은 작업을 동시에 실행하십시오. 계산 집약적 인 작업의 경우 -j +0은 CPU 코어 수 작업을 동시에 실행하므로 유용합니다.

-j -N CPU 코어 수에서 N을 뺍니다. 이 많은 작업을 동시에 실행하십시오. 평가 된 숫자가 1보다 작 으면 1이 사용됩니다. 코어 대신 --use-cpus를 참조하십시오.


1

필자의 경우 세마포어 (Windows의 git-bash에 있음)를 사용할 수 없으므로 작업을 시작하기 전에 N 작업자 사이에서 작업을 분할하는 일반적인 방법을 생각해 냈습니다.

작업이 대략 같은 시간이 걸리면 잘 작동합니다. 단점 중 하나는 근로자 중 한 명이 업무를 수행하는 데 오랜 시간이 걸리면 이미 완료된 다른 사람들은 도움이되지 않는다는 것입니다.

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

0

@PSkocik의 솔루션에 문제가있었습니다 . 내 시스템에는 GNU Parallel을 패키지로 사용할 수 없으므로 sem수동으로 빌드하고 실행할 때 예외가 발생했습니다. 그런 다음 FIFO 세마포어 예제도 시도했지만 통신과 관련된 다른 오류가 발생했습니다.

@eyeApps xargs를 제안했지만 복잡한 유스 케이스에서 작동시키는 방법을 알지 못했습니다 (예를 들어 환영합니다).

다음과 N같이 구성된 작업을 한 번에 최대 처리하는 병렬 작업에 대한 솔루션입니다 _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
}

사용법 예 :

#!/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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.