Bash 코드의 병렬 버전 작성 문제

Bash 코드의 병렬 버전 작성 문제

샘플 Bash 스크립트를 병렬화하려고 하며 및 &와 같은 명령을 시도했습니다 wait. 병렬화하는 효율적인 방법이 무엇인지 알려주세요.

내 현재 코드는 reg2 변수의 제한된 항목으로 잘 작동합니다. 하지만 reg2 변수에는 수백만 개의 항목이 있습니다. 그래서 저는 가장 바깥쪽 루프가 평행해지기를 원합니다. 코드를 병렬화한 후에는 동일한 출력(예: 0,1,2,:,3,4,:,5,6)을 얻습니다.

#!/bin/bash

# array1=$1
# array2=($2)
# reg2=($3)

array1=('bam1' 'bam2' 'bam3' 'bam4' 'bam5' 'bam6' 'bam7')
array2=('cell1' 'cell1' 'cell1' 'cell2' 'cell2' 'cell3' 'cell3')
reg2=('chr1:10484-10572' 'chr1:10589-10632' 'chr1:10636-10661' 'chr1:10665-10690' 'chr1:10694-10719') 

start=`date +%s.%N`

l=${#reg2[@]} # number of regions is 30 million on real data
reg_cov=()
j=0
for r in ${reg2[@]}; do
    
    (cov_array=()
    old_array2_element=${array2[0]}
    
    for i in ${!array1[*]}; do
      new_array2_element=${array2[$i]}
      
      if [[ "$new_array2_element" != "$old_array2_element" ]]; then
        cov_array+=(":")
        old_array2_element=$new_array2_element
      fi
      cov_array+=($i) # in actual code this step takes 4-5 seconds to process
      sleep 2
    done
    
    
    reg_cov+=($(IFS=, ; echo "${cov_array[*]}"))  )
    wait
    
    ((j++)) 
    echo "$j/$l"
done

#echo ${reg_cov[@]}
cov=()
cov+=(${reg_cov[@]})
echo $cov


end=`date +%s.%N`; runtime=$( echo "$end - $start" | bc -l ); runtime=${runtime%.*}; hours=$((runtime / 3600)); minutes=$(( (runtime % 3600) / 60 )); seconds=$(( (runtime % 3600) % 60 ))
echo "==> completed Runtime: $hours:$minutes:$seconds (hh:mm:ss)"

답변1

댓글에서 언급했듯이 수백만 개의 프로젝트에서는 성능상의 이유로 Bash 이외의 거의 모든 것을 사용해야 할 것입니다. 쉘은 일반적으로 그다지 빠르지 않으며 Bash는 가장 느린 것 중 하나입니다. 또한 대규모 배열을 처리할 때 그다지 효율적이지 않을 것이라고 생각하지만 특별히 이에 대한 테스트를 본 적이 없다고 생각합니다.

또한 스크립트는 외부 루프의 각 반복에서 두 개의 하위 쉘을 시작합니다. 하나는 에서 시작하고 (cov_array=()다른 하나는 명령 치환에서 시작합니다 $(IFS=, ; echo "${cov_array[*]}"). Bash에서는 하위 프로세스를 분기하는 작업이 포함됩니다. 적당히 수행하면 그다지 나쁘지 않지만 수백만 번 반복하면 큰 타격을 받기 시작합니다.

또한 각 항목을 처리하는 데 4~5초가 걸리면 하위 프로세스 오버헤드가 그다지 중요하지 않을 수 있습니다. (또한 16배 병렬화로 300만 개의 항목을 처리하는 데 약 10일이 소요되며 항목당 단 몇 초가 소요됩니다. 또는 항목당 4~5초가 소요됩니다.내부에루프 반복? 따라서 시간에 의 항목 수를 곱합니다 array1. 위에서 언급한 대로 3월 초쯤에는 7개의 항목이 있었습니다. 가장 안쪽 단계를 최적화할 수 있는지 고려할 수 있습니다. )

또한 현재로서는 스크립트가 유용한 내용을 인쇄하지 않는다는 점에 유의하십시오. 할당은 reg_cov하위 쉘에 있으므로 궁극적으로 주 프로그램은 이를 볼 수 없으며 출력도 없습니다. 여러 작업을 병렬로 실행하려면 여러 다른 프로세스를 실행해야 하며 필요한 경우 결과를 기본 프로세스로 다시 이동할 수 있도록 준비해야 합니다. 적어도 쉘에서는 자동으로 발생하지 않습니다. 아니면 파일에서 읽고 파일로 인쇄하면 됩니다.

그런 다음 배열 요소를 토큰화하는 비교적 사소한 문제가 있습니다 . 이를 ${reg2[@]}사용해야 합니다 . 또한 실제로 어디에서나 사용하지 않기 때문에 약간 이상해 "${reg2[@]}"보입니다 . 값을 직접 반복할 수 있는 것 같습니다 . index 에 있는 요소만 인쇄하거나 필요한 전체 배열을 인쇄하거나 전체 내용을 인쇄합니다.for i in ${!array1[*]}array1array2echo $cov0covecho "${cov[@]}"echo "${cov[*]}"


작업의 가장 안쪽 단계가 실제로 수행하는 작업과 프로젝트의 출처에 따라 reg2GNU Parallel을 살펴보는 것이 좋습니다. 파일에서 입력을 읽고, 각 항목에 대한 프로세스를 실행하고, 합리적인 순서로 출력을 수집할 수 있습니다.


즉, 셸에서 무언가를 병렬화하려는 경우 이전 게시물에 몇 가지 해결 방법이 있습니다. Bash FOR 루프 병렬화

답변2

무엇을 하려는지 알기가 정말 어렵지만, 3천만 줄이 포함된 파일이 있고 reg2.txt각 줄에 대해 bash 함수를 실행한다고 가정해 보겠습니다.

doit() {
  reg2="$1"
  echo do stuff with "$reg2"
  array1=('bam1' 'bam2' 'bam3' 'bam4' 'bam5' 'bam6' 'bam7')
  for i in ${!array1[*]}; do
     printf "$i "
  done
  echo
}

doit chr1:10484-10572당신은 당신이 옳은 일을 하는지 확인해야 합니다 .

작동하면 다음을 수행할 수 있습니다.

export -f doit
cat reg2.txt | parallel doit

이는 reg2.txt의 각 라인에 대해 실행되며 doitCPU 스레드당 1개의 작업을 병렬로 실행합니다.

답변3

제가 올바르게 이해했다면 외부 루프는 약 3천만 번의 반복이 있고 내부 루프는 약 7번의 반복이 있으며 가장 안쪽 계산에는 4~5초가 걸립니다. 완성하는 데 총 29.9년이 걸립니다! 최선의 경우 64개 코어에 걸쳐 이 접근 방식을 병렬화하면 실행 시간이 약 5.6개월로 줄어들 수 있지만 여전히 비현실적입니다.

가장 좋은 접근 방식은 먼저 4~5초 계산을 위해 코드를 최적화하는 것입니다(표시되지 않음).

관련 정보