对文件进行解胶时,如何解决打开文件的限制?

时间:2018-08-23 18:55:57

标签: bash awk ulimit multiplexing

我经常有大的文本文件(解压缩后的10-100GB)要根据每行中的条形码进行解复用,实际上,生成的单个文件(唯一条形码)的数量在1K到20K之间。我一直在使用awk来完成任务。但是,我注意到,对较大文件进行解胶的速度(与所使用的更多唯一条形码相关)的速度要慢得多(10-20倍)。检查ulimit -n显示每个进程打开文件的限制为4096,因此我怀疑速度下降是由于awk的开销被迫在每次多路分解的文件总数超过一定值时不断关闭并重新打开文件4096。

缺少根访问权限(即限制是固定的),可以使用哪种变通办法来解决此瓶颈?

我确实有每个文件中存在的所有条形码的列表,因此我考虑了分叉多个awk进程,在每个进程中分配了一个互斥的条形码子集(<4096)进行搜索。但是,我担心必须检查每行的条形码以获取组成员身份的开销可能会抵消不关闭文件的好处。

有更好的策略吗?

我还没有嫁给awk,所以欢迎使用其他脚本或编译语言的方法。


具体示例

数据生成(带有条形码的FASTQ)

以下内容生成的数据与我专门处理的数据类似。每个条目包含4行,其中条形码是使用明确的DNA字母的18个字符的单词。

1024个唯一条形码|一百万次读取

cat /dev/urandom | tr -dc "ACGT" | fold -w 5 | \
awk '{ print "@batch."NR"_"$0"AAAAAAAAAAAAA_ACGTAC length=1\nA\n+\nI" }' | \
head -n 4000000 > cells.1K.fastq

16384个唯一条形码|一百万次读取

cat /dev/urandom | tr -dc "ACGT" | fold -w 7 | \
awk '{ print "@batch."NR"_"$0"AAAAAAAAAAA_ACGTAC length=1\nA\n+\nI" }' | \
head -n 4000000 > cells.16K.fastq

awk脚本用于多路分解

请注意,在这种情况下,我将为每个唯一的条形码写入2个文件。

demux.awk

#!/usr/bin/awk -f
BEGIN {
    if (length(outdir) == 0 || length(prefix) == 0) {
        print "Variables 'outdir' and 'prefix' must be defined!" > "/dev/stderr";
        exit 1;
    }
    print "[INFO] Initiating demuxing..." > "/dev/stderr";
}
{
    if (NR%4 == 1) {
        match($1, /.*_([ACGT]{18})_([ACGTN]{6}).*/, bx);
        print bx[2] >> outdir"/"prefix"."bx[1]".umi";
    }
    print >> outdir"/"prefix"."bx[1]".fastq";

    if (NR%40000 == 0) {
        printf("[INFO] %d reads processed\n", NR/4) > "/dev/stderr";
    }
}
END {
    printf("[INFO] %d total reads processed\n", NR/4) > "/dev/stderr";
}

用法

awk -v outdir="/tmp/demux1K" -v prefix="batch" -f demux.awk cells.1K.fastq

或类似的cells.16K.fastq

假设您是唯一运行awk的计算机,则可以使用以下命令验证打开文件的大约数量

lsof | grep "awk" | wc -l

观察到的行为

尽管文件大小相同,但具有16K唯一条形码的文件的运行速度比只有1K唯一条形码的文件慢10到20倍。

1 个答案:

答案 0 :(得分:3)

没有看到任何示例输入/输出或您当前正在执行的脚本,这非常猜测,但是如果您当前在字段1中有条形码并且正在执行(假设使用GNU awk,那么您就没有自己的代码来管理打开文件):

awk '{print > $1}' file

然后,如果确实要管理打开的文件,则将其更改为:

sort file | '$1!=f{close(f};f=$1} {print > f}'

当然,以上是假设这些barcoode值是什么,哪个字段保存它们,什么字段分开,输出顺序是否必须与原始匹配,您的代码可能正在做的其他事情会变慢随着输入的增加等,因为您还没有向我们展示过这些。

如果这还不是您所需要的,那么请编辑您的问题以包括缺少的MCVE。


鉴于您的脚本和输入的信息是4行块,这是更新的问题,我会通过在每个记录的开头添加键“ bx”值并使用NUL来分隔4个字符来解决此问题行块,然后使用NUL作为记录分隔符进行排序和后续的awk:

$ cat tst.sh
infile="$1"
outdir="${infile}_out"
prefix="foo"

mkdir -p "$outdir" || exit 1

awk -F'[_[:space:]]' -v OFS='\t' -v ORS= '
    NR%4 == 1 { print $2 OFS $3 OFS }
    { print $0 (NR%4 ? RS : "\0") }
' "$infile" |
sort -z |
awk -v RS='\0' -F'\t' -v outdir="$outdir" -v prefix="$prefix" '
BEGIN {
    if ( (outdir == "") || (prefix == "") ) {
        print "Variables \047outdir\047 and \047prefix\047 must be defined!" | "cat>&2"
        exit 1
    }
    print "[INFO] Initiating demuxing..." | "cat>&2"
    outBase = outdir "/" prefix "."
}
{
    bx1   = $1
    bx2   = $2
    fastq = $3

    if ( bx1 != prevBx1 ) {
        close(umiOut)
        close(fastqOut)
        umiOut   = outBase bx1 ".umi"
        fastqOut = outBase bx1 ".fastq"
        prevBx1  = bx1
    }

    print bx2   > umiOut
    print fastq > fastqOut

    if (NR%10000 == 0) {
        printf "[INFO] %d reads processed\n", NR | "cat>&2"
    }
}
END {
    printf "[INFO] %d total reads processed\n", NR | "cat>&2"
}
'

针对问题中描述的生成的输入文件运行时:

$ wc -l cells.*.fastq
4000000 cells.16K.fastq
4000000 cells.1K.fastq

结果是:

$ time ./tst.sh cells.1K.fastq 2>/dev/null

real    0m55.333s
user    0m56.750s
sys     0m1.277s

$ ls cells.1K.fastq_out | wc -l
2048

$ wc -l cells.1K.fastq_out/*.umi | tail -1
1000000 total

$ wc -l cells.1K.fastq_out/*.fastq | tail -1
4000000 total


$ time ./tst.sh cells.16K.fastq 2>/dev/null

real    1m6.815s
user    0m59.058s
sys     0m5.833s

$ ls cells.16K.fastq_out | wc -l
32768

$ wc -l cells.16K.fastq_out/*.umi | tail -1
1000000 total

$ wc -l cells.16K.fastq_out/*.fastq | tail -1
4000000 total