샘플 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[*]}
array1
array2
echo $cov
0
cov
echo "${cov[@]}"
echo "${cov[*]}"
작업의 가장 안쪽 단계가 실제로 수행하는 작업과 프로젝트의 출처에 따라 reg2
GNU 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의 각 라인에 대해 실행되며 doit
CPU 스레드당 1개의 작업을 병렬로 실행합니다.
답변3
제가 올바르게 이해했다면 외부 루프는 약 3천만 번의 반복이 있고 내부 루프는 약 7번의 반복이 있으며 가장 안쪽 계산에는 4~5초가 걸립니다. 완성하는 데 총 29.9년이 걸립니다! 최선의 경우 64개 코어에 걸쳐 이 접근 방식을 병렬화하면 실행 시간이 약 5.6개월로 줄어들 수 있지만 여전히 비현실적입니다.
가장 좋은 접근 방식은 먼저 4~5초 계산을 위해 코드를 최적화하는 것입니다(표시되지 않음).