User Tools

Site Tools


gnu_parallel

GNU Parallel 활용 가이드

컴퓨터 클러스터에 대한 전문적인 지식 없이도 간편하게 병렬 작업을 하도록 도와주는 유틸리티가 있으니 그것이 바로 GNU Parallel이다. 작게는 단일 호스트에서 다중 코어에 능률적으로 job을 배분하는 것으로부터 시작하여 SSH로 접속되는 다중 원격 호스트를 통하여 병렬 작업을 하는 것까지 그 활용의 폭은 매우 넓다. 이것에 익숙해지면 앞으로는 for loop나 xargs를 쓸 일이 없을 것이다.

로컬 서버에에서 GNU Parallel을 사용하는 아주 단순한 사례를 생각해 보자. 이 서버에는 16개의 코어가 있다고 가정하자. 나에게 주어진 일은 현 디렉토리에 있는 100개의 fastq 파일을 압축하는 것이다. 가장 단순하고도 비능률적인 방법은 다음과 같이 실행 하는 것이다.

$ gzip *fastq

만약 하부 디렉토리에 있는 fastq 파일까지 찾아서 압축하려면 find 명령어를 쓰면 된다. 그러나 어떤 경우이든, 파일을 순차적으로 하나씩 처리하므로 많은 시간이 걸리게 된다. GNU Parallel을 사용하면 작업할 파일을 여러 core로 보내서 동시에 일을 하게 만든다. 다음의 명령어로는 총 8개의 gzip job을 동시에(즉 parallele하게) 실행한다. 다시 말하자면 ::: 뒤편의 인수 리스트에서 한번에 8개의 fastq 파일을 골라서(순서는 무작위적임) 이를 8개의 코어로 보내어 gzip 압축을 하게 만든다는 것이다. 이 사례에서는 파일을 잘게 자르지는 않는다(그것도 병렬 작업의 한 방법이 된다).

$ parallel -j 8 gzip ::: *.fastq

물론 이것은 극단적으로 단순한 활용 사례에 불과하다. 약간 고급스런 응용 사례를 생각해 보자. 현 디렉토리 아래에 존재하는 모든 fastq 파일을 찾아서 압축을 하려면 다음과 같이 한다.

$ find . -name "*.fastq" | parallel gzip
$ parallel -a fasta_files_fof gzip (압축할 파일 목록이 있는 경우)
$ parallel -a gzipped_fasta_files_fof gzip -d (압축을 해제할 파일 목록이 있는 경우)

결과 파일을 구조적으로 잘 저장한다든가, 실행 순서를 지정한다든가, command line을 정교하게 짜려면 매뉴얼을 보고 좀 더 공부를 해야 한다. GNU Parallel은 SSH로 연결된 remote server를 사용하게 만들 수도 있다.

아주 초급 수준을 벗어난 활용 사례를 설명해 보겠다. 수십개의 염기서열 파일에 대해서 EMBOSS의 restrict(report restriction enzyme cleavage sites in a nucleotide sequence)를 실행하고자 한다. 입력물로 택할 염기서열 파일명과 결과를 출력파일명은 각각 sourcelist와 destlist에 수록되었다고 가정하자. 동시에 8개의 job을 수행하려면 다음과 같이 명령하면 된다. {1}과 {2}의 순서만 잘 지킨다면 -sequence나 -outfile은 생략해도 된다.

$ parallel -a sourcelist -a destlist -j8 restrict -sitelen 4 -enzymes EcoRI,BamHI,HindIII -sequence {1} -outfile {2}

만약 어떤 파일에 완벽한 상태의 command가 여러 줄 들어있고, 이를 병렬 실행하려면 어떻게 하면 좋을까? 다시말해서 parallel 명령어에 인수를 전달하는 것이 아니라 명령행 자체를 제공하려면? 아주 간단하다.

$ parallel -a sourcefile {}

{.}, {/} 등은 sourcefile에서 넘어오는 라인에 대하여 다양한 조작을 할 수 있게 만들어 준다. SRR_Acc_List.txt에 SRA Run accession이 한 줄에 하나씩 들어있다고 가정하자. 이를 일괄적으로 다운로드하여 fastq로 전환하는 방법을 알아보자.

$ parallel -j 1 prefetch {} ::: $(cat SRR_Acc_List.txt)
$ parallel -j 1 fastq-dump --skip-technical -F --split-files -O fastq {} ::: $(cat SRR_Acc_List.txt)

상세한 설명은 man parallel을 해 보라.

외부 자료

Local server에서 사용하기

CPU와 core 수 알아내기

$ parallel --number-of-cpus
2
$ parallel --number-of-cores
32

이런 바보! 그냥 명령행에서 nproc라고만 치면 된다!

BLAST 실행하기

Fasta file을 10 kilobyte 단위로 자르되 실제로 서열 ID 라인('>sequence_ID')으로 시작하게 만들어서 여러 core에 작업을 보낸 뒤 하나로 합친다. 아래의 사례에서는 하나의 CPU core에 job을 하나씩 할당한다.

$ cat amino_acids.faa | parallel --block 10k --recstart '>' --pipe blastp -evalue 0.01 -outfmt 6 -db prot2003-2014.fa -query - > result.blastout

인수 목록이 파일로 존재할 때

수백개의 fastq file을 interleaved form으로 바꾸는 경우를 생각해 보자. fwd와 rev file, 그리고 최종 파일(interleaved)의 이름이 tsv 파일 하나에 들어있다고 가정한다.

$ cat list.tsv
file00_1.fastq  file00_2.fastq  file00.pe.fastq
file01_1.fastq  file02_1.fastq  file01.pe.fastq
...

이번에는 콜론을 4개 연이어 붙어야 한다. 동시 작업 수는 24개로 제한하였다(-j24). list.tsv의 첫 줄이 헤더라면 –header 옵션을 주어서 건너뛰게 할 수 있다.

$ parallel --colsep "\t" -j24 interleave-reads.py -o {3} {1} {2} :::: list.tsv

출력을 파일로 저장하기

parallel로 실행하는 명령어가 표준 출력으로 무엇인가를 내놓는다면, 이를 저장할 수 있다.

$ parallel --files __do_something__ ::: *

출력물은 /tmp 아래에 다음과 같은 형식으로 저장된다.

/tmp/parR1SSD.par
/tmp/parjpwJL.par

저장되는 위치를 바꾸려면 다음과 같이 환경변수를 설정한다.

$ TMPDIR=/var/tmp parallel --files __do_something__ ::: *

Remote server 활용하기

remote host를 사용하려면 미리 설정해 둘 것이 많다. microbe와 proton 서버의 IP address는 /etc/hosts 파일에 있어야 하고, 각 호스트로 암호 없이 ssh login이 되도록 만들어 두어야 한다. 또한 기본 포트인 22번이 아니라 3030번 포트를 통해서 SSH 접속을 한다면 환경변수 $PARALLEL_SSH를 다음과 같이 선언해 두어야 한다.

$ export PARALLEL_SSH='/usr/bin/ssh -p 3030 ' # 3030과 따옴표 사이에 공백이 반드시 필요함.

Warming up

각 리모트 호스트의 이름을 알아내자. 동일한 명령어를 리모트 호스트 각자에서 똑같이 실행하되 인수는 없다(–nonall). :는 local server를 의미한다.

$ parallel --nonall -S :,microbe,proton hostname 
tube.kribb.re.kr
microbe.kribb.re.kr
proton.kribb.re.kr

원래 'parallel 명령어 ::: 인수' 형식으로 실행해야 한다. 명령어를 생략하면 인수 자리에 있는 문자열을 명령어로 해석한다. 앞서도 나왔던 –tag는 –onall 또는 -nonall과 같이 쓰여서 호스트명을 결과와 같이 출력한다. –onall은 동일한 명령어를 모든 리모트 호스트에서 실행하되 인수를 공급함을 의미한다.

$ parallel --onall --tag -S :,microbe,proton ::: hostname 
:	tube.kribb.re.kr
microbe	microbe.kribb.re.kr
proton	proton.kribb.re.kr

각 서버에 설치된 python 버전이 궁금하다.

$ parallel --nonall --tag -S :,microbe,proton  python -V
:	Python 2.7.13
microbe	Python 2.6.6
proton	Python 2.6.6

원격 호스트에 장착된 CPU core의 수를 알고싶다. nproc보다 더욱 상세한 CPU 정보가 궁금하다면 lscpuless /proc/cpuinfo를 명령어로 주면 된다. 단, 화면에 길게 출력되므로 알아서 활용하라.

$ parallel --tag --nonall -S :,microbe,proton nproc
:	32
microbe	24
proton	16

원격 호스트 중 어떤 것은 linuxbrew를 통해서 python 상위버전을 설치해 두었다. 이를 실행하려면 정상적으로 로그인하여 linuxbrew 환경으로 수정된 .bash_profile를 적용해야 하지만 parallel을 통해서 원격 호스트의 명령어를 실행하려면 이 과정을 거치지 못한다. 다음과 같이 해 보자.

$ parallel --nonall --tag -S :,microbe,proton source ~/.bash_profile\; python -V
:	Python 2.7.13
microbe	Python 2.6.6
proton	Python 2.7.12

원격 호스트에서 명령어가 아닌 스크립트를 실행하려면 좀 더 치밀한 접근이 필요하다. 이것은 공부를 한 뒤에 나중에 알아보도록 한다.

예제: blast의 병렬 실행

공유 저장소를 사용하는 경우

공유 저장소를 사용하지 않는 경우

blast search를 여러 remote host에서 나누어서 하려면 어떻게 해야 될까? 현재 접속한 local host와 remote host(microbe and proton)에서 COG database(prot2003-2014.fa를 formatdb한 것)에 대한 blast search를 해야 한다. 세 개의 host가 NFS로 저장장치를 공유하지 않은 상황에서는 데이터베이스를 먼저 각 호스트로 복사해야 한다. parallel에서는 공통으로 쓰이는 DB file의 복사(심지어 작업 후 삭제까지!)를 지원한다.

두 개의 remote host에 amino_acid.faa라는 파일을 공통적으로 전송하고 blast DB를 만든다. 호스트명의 슬래쉬 앞에 있는 숫자는 작업에 할당할 CPU 숫자이다.

$ export PARALLEL_SSH='/usr/bin/ssh -p 3030 ' # 3030과 따옴표 사이의 공백에 유의하라.
$ parallel --basefile amino_acids.faa --nonall --tag -S 2/microbe,2/proton makeblastdb -in amino_acids.faa -dbtype prot -out my_blast_db

이번에는 불필요하게 남아있다고 생각되는 파일을 지워버리자.

$ parallel --nonall --tag -S microbe,proton rm amino_acids.faa my_blast_db.*

blast용 DB 파일을 하나로 묶어서 local host로 보낸 뒤 압축을 푸는 것이 좋을까, 혹은 fasta file을 전송하여 각 remote host에서 makeblastdb를 하는 것이 좋을까? 얼핏 드는 생각으로는 이미 만들어진 blast DB 파일을 다른 필요한 파일과 같이 묶어서 전송하는 것이 낫다고 본다.

실제 활용 사례

본 사례는 오류가 있을 수도 있다!

COG 2014 database(prot2003-2004.fa)에 대해서 Query protein의 psiblast를 실시하는 예제이다. psiblast의 옵션은 COGnitor 프로그램의 매뉴얼을 참조하였다. COG blast DB를 microbe와 proton 2 개의 서버로 전송한 다음 Query protein set에 대한 psiblast를 local server와 microbe 및 proton에서 실시한다. parallel 명령어에서는 '-'와 '–'을 잘 구별해야 된다.

$ export PARALLEL_SSH='/usr/bin/ssh -p 3030 ' # 3030과 따옴표 사이의 공백에 유의하라.
$ makeblastdb -in  prot2003-2014.fa -dbtype prot -out COGs
$ tar cvf DB.tar COGs.p*; du -sh DB.tar
COGs.phr
COGs.pin
COGs.psq
906M	DB.tar
$ parallel --basefile DB.tar --nonall --tag -S microbe,proton tar xvf DB.tar
microbe	COGs.phr
microbe	COGs.pin
microbe	COGs.psq
proton	COGs.phr
proton	COGs.pin
proton	COGs.psq
$ mkdir BLASTff
$ cat Query.fa | parallel -S 12/:,4/microbe,4/proton --block 10k \
--recstart '>' --pipe psiblast -db COGs -show_gis -outfmt 7 \
-num_descriptions 1000 -num_alignments 1000 -dbsize 100000000 \
-comp_based_stats T -seg yes -query - > BLASTff/QueryCOGs.tab
$ parallel --nonall --tag -S microbe,proton rm COGs.p* DB.tar
$ rm DB.tar # local server의 것 지우기

마치 작업을 실행하는 각 remote server의 BLASTff/QueryCOGs.tab 파일에 결과가 저장되는 것 아닌가하는 착각을 불러일으키지만, parallel 작업을 시작한 local server의 지정된 파일에 결과가 잘 저장되었다. 물론 query의 순서대로 저장되지는 않았다.

https://www.reddit.com/r/linux/comments/1ka8mn/transient_beowulf_clustering_with_gnu_parallel/

gnu_parallel.txt · Last modified: 2021/03/17 13:09 by 127.0.0.1