bash - 解复用文件时如何解决打开文件限制?

标签 bash awk ulimit multiplexing

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

缺少 root 访问权限(即限制是固定的),可以使用哪些变通方法来规避此瓶颈?

我确实有每个文件中存在的所有条形码的列表,因此我考虑过 fork 多个 awk 进程,其中每个进程都分配有一个相互排斥的条形码子集 (< 4096) 以供搜索。但是,我担心必须检查每一行的条形码以获得集合成员资格的开销可能会抵消不关闭文件的好处。

有没有更好的策略?

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


具体例子

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

以下生成的数据与我正在使用的数据类似。每个条目由 4 行组成,其中条形码是使用无歧义 DNA 字母表的 18 个字符的单词。

1024 个独特的条形码 | 100 万次阅读

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 个独特的条形码 | 100 万次阅读

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 个文件。

解复用器.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 中有条形码并且正在做(假设 GNU awk,那么您没有自己的代码来管理打开文件):

awk '{print > $1}' file

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

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

当然,上面是假设这些条形码值是什么,哪个字段保存它们,分隔字段的是什么,输出顺序是否必须与原始顺序匹配,您的代码可能还做了哪些事情会变慢随着输入的增长等等,因为您还没有向我们展示任何这些。

如果这不是您需要的全部内容,请编辑您的问题以包含缺少的 MCVE。


根据您的脚本更新问题以及输入是 4 行 block 的信息,我将通过在每条记录的前面添加键“bx”值并使用 NUL 分隔 4- 来解决这个问题行 block 然后使用 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

关于bash - 解复用文件时如何解决打开文件限制?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51992571/

相关文章:

Bash 在 awk 打印中包含字符之间的字符串

string - 如何在 CSV 文件末尾多次添加相同的字符串?

Linux:如何更改进程可以打开的最大文件数?

linux - 如何以编程方式检测 Ubuntu 上的软件包和操作系统版本

linux树命令包含文件的头内容

git - 从 git 中提取文件名的 Bash 脚本发生了什么变化

macos - 使用 Bash 解析 ifconfig 以仅获取我的 IP 地址

awk - 每条记录后的空行和换行符中的列

相当于 ulimit -n 的 Windows

java - ULIMIT:如何永久设置限制