GNU 并行和脚本未启动

GNU parallel and script not starting

我正在写一篇关于一些生物信息学工作的学术论文(我会按照作者的要求引用它;)),我需要加快我的 bash.

它基本上是一个 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[]=}} {if ( in st_array) {print [=10=], st_array[], "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=""
  # 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 中看到单个进程 运行ning 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=""
    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[]=}} {if ( in st_array) {print [=14=], st_array[], "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=""
    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=""
        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[]=}} {if ( in st_array) {print [=15=], st_array[], "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 核机器上一次 64 个。

但在您的情况下,内循环中的值 do 取决于外循环中的值。这意味着您需要在开始任何作业之前计算这些值。这也意味着你不能在外循环中有你的脚本输出信息。

#!/bin/bash

sam_awk() {
        bam_file_target=""
        sample=""
        ID_File=""

        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[]=}} {if ( in st_array) {print [=18=], st_array[], "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 分钟,您的命令行就会爱上它。