while 루프를 입력하기 위해 grep을 사용하는 Bash 스크립트의 속도를 향상시킵니다.

while 루프를 입력하기 위해 grep을 사용하는 Bash 스크립트의 속도를 향상시킵니다.

다음 시나리오를 사용하여 많은 줄(>500Mb)로 구성된 파일에 대해 이 스크립트가 작동하도록 합니다.

odd lines: >BLA_BLA lenght_XX cov.XX
even lines: AGCAGCAGACTCAGACTACAGAT  # on even lines there's a DNA sequence

그 기능은 "cov" 이후의 값을 다시 계산하는 것입니다. 인수로 전달된 인수를 사용하고 이전 인수를 대체하고 "G"와 "C"가 DNA 서열에 들어가는 짝수 행의 백분율을 계산합니다.

따라서 출력은 다음과 같습니다.

> BLA_BLA lenght_XX
> nucleotidic_cov XX
> DNA seq (the same of even lines)
> GC_CONT: XX

코드는 다음과 같습니다(루프만 해당).

K=$(($READLENGHT - $KMER + 1))
Y=$(echo "scale=4; $K / $READLENGHT" | bc)

while read odd; do
    echo -n "${odd##}" | cut -d "_" -f 1,2,3,4 && printf "nucleotide_cov: " 
    echo "scale=4;${odd##*_} / $Y" | bc 
    read even
    echo "${even##}" &&
    ACOUNT=$(echo "${even##}" |  sed -e "s/./&\n /g" | grep -c "A")  
    GCOUNT=$(echo "${even##}" |  sed -e "s/./&\n /g" | grep -c "G")
    CCOUNT=$(echo "${even##}" |  sed -e "s/./&\n /g" | grep -c "C")
    TCOUNT=$(echo "${even##}" |  sed -e "s/./&\n /g" | grep -c "T")
    TOTALBASES=$(($ACOUNT+$GCOUNT+$CCOUNT+$TCOUNT))
    GCCONT=$(($GCOUNT+$CCOUNT))
    printf "GC_CONT: " 
    echo "scale=2;$GCCONT / $TOTALBASES *100" | bc  
done < "$1"

16코어 서버에서 대용량 텍스트 파일(500Mb 이상)을 실행할 때 속도가 매우 느립니다. 이 스크립트의 속도를 향상시키는 방법에 대한 아이디어가 있습니까?

편집하다

요청 시 필요한 I/O는 Pastebin을 통해 제공됩니다.https://pastebin.com/FY0Z7kUW

답변1

셸에서 합리적으로 수행할 수 있는 작업의 한계에 도달했습니다. AWK, Perl 또는 Python과 같은 언어로 스크립트를 다시 작성해야 합니다. 이러한 고급 언어를 사용하면 모든 텍스트 처리에 대해 여러 프로세스를 실행하지 않아도 되며 이를 위해 내장된 기능을 사용할 수 있습니다.

답변2

백분율 계산은 다음과 같은 단일 작업으로 축소될 수 있습니다.

 echo "${even##}" | awk '{x=gsub(/[ACT]/,""); y=gsub(/G/,""); printf "GC_CONT : %.2f%%\b", (y*100)/(x+y) }'

gsub는 패턴을 대체하고 패턴이 대체한 횟수를 반환합니다. 이는 백분율을 빠르게 계산하는 데 사용할 수 있습니다.

awk에서는 홀수 및 짝수 라인을 처리할 수도 있습니다. 이상한 줄로 무엇을 하고 있는지는 확실하지 않지만 전체 기능을 awk에 넣을 수 있습니다.

awk -F '_' -v Y="$Y" '{ if(NR%2==1) {
    printf "%s %s %s %s %s\nnucleotidic_cov : %.4f\n",$1,$2,$3,$4,$5, ($6 / Y)
} else {
    x=gsub(/[AT]/,""); 
    y=gsub(/[GC]/,""); 
    printf "GC_CONT : %.2f%%\n", (y*100)/(x+y)
    }
 }' large_file

편집하다:OP의 요청에 따라 홀수 줄에 대한 if 블록을 변경했습니다. gsub는 "cov"를 삭제합니다. 숫자로 판단합니다. 쉘 변수 $Y를 awk에 전달한 후 이제 원하는 형식으로 분할하여 인쇄할 수 있습니다.

여러 작업 대신 단일 awk 스크립트를 사용하면 작업 속도가 크게 향상됩니다.

답변3

프로그램이 병렬화되지 않으면(너무 많이) 코어 수는 중요하지 않습니다.

sed와 grep 대신 wcand를 사용하면 작업 속도가 빨라질 수 있습니다.tr

ACOUNT=$(echo "${even##}" | tr -d [^A] | wc -m)

그러나 실제로 가장 큰 문제는 쉘이 빠르고 더러운 작업을 위해 프로그래밍하기 쉽지만 원시 처리 능력 측면에서 작업에 적합한 도구가 아니라는 것입니다. 스레딩 기능도 있는 Perl 또는 Python과 같은 보다 복잡한 프로그래밍 언어를 사용하는 것이 좋습니다(따라서 모든 코어를 사용할 수 있음).

Perl에서는 다음과 같이 이를 수행할 수 있습니다.

#!/usr/bin/perl -w
use strict;
use warnings;

my $y = ...;                              # calculate your Y value here
while(my $odd = <ARGV>) {                 # Read a line from the file(s) passed
                                          # on the command line
    chomp $odd;                           # lose the newline
    my @split = split /_/, $odd;          # split the read line on a "_" boundary
                                          # into an array
    print join("_", @split[0..3]) . "\n"; # print the first four elements of the
                                          # array, separated by "_"
    print $split[$#split] / $y . "\n";    # Treat the final element of the
                                          # @split array as a number, divide it
                                          # by $y, and output the result
    my %charcount = (                     # Initialize a hash table
        A => 0,
        G => 0,
        C => 0,
        T => 0
    );
    my $even = <ARGV>;                    # read the even line
    chomp $even;
    foreach my $char(split //,$even) {    # split the string into separate
                                          # characters, and loop over them
        $charcount{$char}++;              # Count the correct character
    }
    my $total = $charcount{A} + $charcount{G} + $charcount{C} + $charcount{T};
    my $gc = $charcount{G} + $charcount{C};
    my $perc = $gc / $total;
    print "GC_CONT: $perc\n";             # Do our final calculations and
                                          # output the result
}

참고: 테스트되지 않았습니다("Perl이 이 코드를 허용합니까" 제외).

Perl에 대해 더 자세히 알고 싶다면 실행 perldoc perlintro하고 시작하세요 ;-)

답변4

긴 파일을 한 줄씩 읽고 각 반복에서 여러 명령을 실행하고 있습니다. 직면하는 주요 문제는 이러한 계산을 실행하고 한 번에 매우 작은 파일 청크를 읽는 데 지연이 있다는 것입니다.

Stephen Kitt의 답변은 훌륭합니다. 파일 내용을 캐시하고 문자열 작업을 보다 효율적으로 실행할 수 있는 더 높은 수준의 언어로 다시 작성하고 싶습니다.

스토리지 및 파일 시스템 성능을 제외하려면 다음 명령을 사용하여 RAM에서 파일을 로드할 수 있습니다.

# mkdir /mnt/tmpfs
# mount -t tmpfs -o size=1024M tmpfs /mnt/tmpfs
# cp <input_file> /tmp/tmpfs
# <script> /tmp/tmpfs/<input_file>

I/O가 제한되어 있으므로 프로세스가 더 빨라집니다. 그러나 C, Ruby 또는 Python으로 다시 작성하면 결코 좋아질 수 없습니다.

관련 정보