GNU并行和脚本未启动

问题描述

我正在撰写有关某些生物信息学工作的学术论文(我将按作者的要求引用它;)),我需要加快我的速度。

基本上,这是一个bash脚本,该脚本运行循环来迭代文件并使用awk查找字符串。

我遵循了手册,并使用parallel -a ./script.sh。我在使用变量时遇到了问题,因此我将其更改为-q,尽管没有错误消息,但脚本似乎根本无法启动。

我可能做错了什么,但我不明白。以前,我必须用:::来传递它,因为我有一个输入文件,但是此脚本没有任何文件

脚本:

#!/bin/bash
files_chrM_ID="concat_chrM_*"
bam_directory="../bam/"
for ID_file in ${files_chrM_ID}
do
    echo "$(date +%H:%I:%s) $ID_file is being treated"
    sample=${ID_file: -12}
    sample=${sample:0:8}
    echo "$(date +%H:%I:%s) $sample is being treated"
    for bam_file_target in "${bam_directory}"*"${sample}"*".bam"
    do
        echo $bam_file_target // $sample
        out_file=${ID_file:0:-4}_ON_${bam_file_target:8:-4}.sam
        echo "$out_file will be created"
        echo "samtools and awk starting"
        
        samtools view -@ 6 $bam_file_target | awk -v st="$ID_file" 'BEGIN {OFS="\t";ORS="\r\n"; while (getline < st) {st_array[$1]=$2}} {if ($1 in st_array) {print $0,st_array[$1],"target"}}' >> $out_file
        echo "$out_file done."
    done
done

和我的命令:

parallel -q ./script.sh

解决方法

GNU Parallel并不是魔术:您不能告诉它并行化任何脚本。

相反,您需要告诉它要并行化什么以及如何并行化。

通常,您需要考虑必须生成要并行运行的命令列表,然后将此列表提供给GNU Parallel。

在脚本中,您有2个for循环和一个管道。可以通过使用GNU Parallel来并行化这三个对象。但是,不确定它是否有意义:并行化会产生开销,如果当前的实现方式最佳地利用了CPU和磁盘资源,那么并行化将不会带来加速。

像这样的for循环

for x in x-value1 x-value2 x-value3 ... x-valueN; do
  # do something to $x
done

通过以下方式并行化:

myfunc() {
  x="$1"
  # do something to $x
}
export -f myfunc
parallel myfunc ::: x-value1 x-value2 x-value3 ... x-valueN

A | B | C较慢的B形式的管道通过以下方式并行化:

A | parallel --pipe B | C

因此,首先要确定瓶颈。

对此top真的很有用。如果您发现top中有一个100%运行的进程是并行化的不错选择。

如果没有,那么您可能会受到磁盘速度的限制,而GNU Parallel几乎不会加快这种速度。

您尚未包含测试数据,因此我无法运行您的脚本并为您确定瓶颈。但是我对samtools有经验,samtools view一直是我脚本中的瓶颈。因此,让我们假设这里也是这种情况。

samtools ... | awk ...

这不适合A | B | C较慢的B模板,因此我们不能使用parallel --pipe来加快速度。但是,如果awk是瓶颈,那么我们可以使用parallel --pipe

因此,让我们看一下两个for循环。

很容易并行化外循环:

#!/bin/bash
files_chrM_ID="concat_chrM_*"

do_chrM() {
    ID_file="$1"
    bam_directory="../bam/"
    echo "$(date +%H:%I:%S) $ID_file is being treated"
    sample=${ID_file: -12}
    sample=${sample:0:8}
    echo "$(date +%H:%I:%S) $sample is being treated"
    for bam_file_target in "${bam_directory}"*"${sample}"*".bam"
    do
        echo $bam_file_target // $sample
        out_file=${ID_file:0:-4}_ON_${bam_file_target:8:-4}.sam
        echo "$out_file will be created"
        echo "samtools and awk starting"
        
        samtools view -@ 6 $bam_file_target | awk -v st="$ID_file" 'BEGIN {OFS="\t";ORS="\r\n"; while (getline < st) {st_array[$1]=$2}} {if ($1 in st_array) {print $0,st_array[$1],"target"}}' >> $out_file
        echo "$out_file done."
    done
}
export -f do_chrM

parallel do_chrM ::: ${files_chrM_ID}

如果${files_chrM_ID}的数量超过CPU线程的数量,这非常好。但是,如果不是这种情况,我们还需要并行化内部循环。

这有点棘手,因为我们需要导出一些变量以使它们对do_bam调用的parallel可见:

#!/bin/bash
files_chrM_ID="concat_chrM_*"

do_chrM() {
    ID_file="$1"
    bam_directory="../bam/"
    echo "$(date +%H:%I:%S) $ID_file is being treated"
    sample=${ID_file: -12}
    sample=${sample:0:8}
    # We need to export $sample and $ID_file to make them visible to do_bam()
    export sample
    export ID_file
    echo "$(date +%H:%I:%S) $sample is being treated"
    do_bam() {
        bam_file_target="$1"
        echo $bam_file_target // $sample
        out_file=${ID_file:0:-4}_ON_${bam_file_target:8:-4}.sam
        echo "$out_file will be created"
        echo "samtools and awk starting"
        
        samtools view -@ 6 $bam_file_target | 
          awk -v st="$ID_file" 'BEGIN {OFS="\t";ORS="\r\n"; while (getline < st) {st_array[$1]=$2}} {if ($1 in st_array) {print $0,"target"}}' >> $out_file
        echo "$out_file done."
    }
    export -f do_bam
    parallel do_bam ::: "${bam_directory}"*"${sample}"*".bam"
}
export -f do_chrM

parallel do_chrM ::: ${files_chrM_ID}

但是,这可能会使服务器超载:内部并行不会与外部并行通信,因此,如果您在64核计算机上运行此并行,则可能会并行运行64 * 64作业(但前提是必须有足够的文件匹配) concat_chrM_*"${bam_directory}"*"${sample}"*".bam")。

在这种情况下,将外部parallel并行地限制为1或2个作业将很有意义:

parallel -j2 do_chrM ::: ${files_chrM_ID}

这最多将在64核计算机上并行运行2 * 64作业。

但是,如果您希望一直并行运行64个作业,那么它将变得相当棘手:如果内部循环的值不依赖于外部循环,这将非常简单,因为那样您可能只是做了类似的事情:

parallel do_stuff ::: chrM_1 ... chrM_100 ::: bam1.bam ... bam100.bam

这将生成chrM_X,bamY.bam的所有组合,并在64核计算机上一次并行运行这些组合。

但是在您的情况下,内循环 do 中的值取决于外循环中的值。这意味着您需要在开始任何作业之前计算值。这也意味着您无法在外部循环中获得脚本输出信息。

#!/bin/bash

sam_awk() {
        bam_file_target="$1"
        sample="$2"
        ID_File="$3"

        echo "$(date +%H:%I:%S) $ID_file is being treated"
        echo "$(date +%H:%I:%S) $sample is being treated"

        echo $bam_file_target // $sample
        out_file=${ID_file:0:-4}_ON_${bam_file_target:8:-4}.sam
        echo "$out_file will be created"
        echo "samtools and awk starting"

        samtools view -@ 6 $bam_file_target |
          awk -v st="$ID_file" 'BEGIN {OFS="\t";ORS="\r\n"; while (getline < st) {st_array[$1]=$2}} {if ($1 in st_array) {print $0,"target"}}' >> $out_file       
        echo "$out_file done."
}

files_chrM_ID="concat_chrM_*"
bam_directory="../bam/"
for ID_file in ${files_chrM_ID}
do
# Moved to inner
#    echo "$(date +%H:%I:%S) $ID_file is being treated"
    sample=${ID_file: -12}
    sample=${sample:0:8}
# Moved to inner
#    echo "$(date +%H:%I:%S) $sample is being treated"
    for bam_file_target in "${bam_directory}"*"${sample}"*".bam"
    do
        echo "$bam_file_target"
        echo "$sample"
        echo "$ID_File"
    done
done | parallel -n3 sam_awk

鉴于您还没有提供给我们任何测试数据,所以我无法测试这些脚本是否会真正运行,因此它们中可能有错误。

如果您尚未这样做,请至少阅读“ GNU Parallel 2018”的第1 + 2章(位于 http://www.lulu.com/shop/ole-tange/gnu-parallel-2018/paperback/product-23558902.html或 请将其下载到:https://doi.org/10.5281/zenodo.1146014

这应该花费您不到20分钟的时间,并且您的命令行会因此而爱您。