【问题标题】:How to efficiently combine N files, two at a time如何有效地合并N个文件,一次两个
【发布时间】:2022-07-05 05:01:08
【问题描述】:

基于another question中的讨论。

有些工具一次只接受两个输入文件,但最终的合并输出需要合并 N 个输出文件。示例包括粘贴和一些床或 vcf 工具。假设存在样本列表并且二元运算是关联的,(a+b)+c == a+(b+c)。必须通过重复组合输入和中间文件来生成所需的合并输出。如何有效地合并文件?

【问题讨论】:

    标签: snakemake


    【解决方案1】:

    我将介绍的两种解决方案是顺序组合输入文件和递归地构建中间文件作为二叉树。对于每一个,请考虑将数百个样本与以下蛇文件开头粘贴在一起:

    ids = list('abcdefghijklmnopqrstuvwxyz')
    samples = expand('{id1}{id2}', id1=ids, id2=ids)  # 676 samples, need not be numbers
    # aa, ab, ac, .., zz
    
    rule all:
        input: 'merged.txt'
    
    rule generate_data:
        output: 'sample_{sample}.txt'
        shell:
            'echo {wildcards.sample} > {output}'
    

    顺序解决方案

    顺序解决方案相当容易记住和理解。您将文件 1 和 2 合并到一个临时文件中,然后将临时文件与文件 3 合并,...直到文件 N。您可以使用 run 指令和 shell 命令执行此操作,但我将把它作为一个shell 指令

    rule merge:
        input:
            first_files=expand('sample_{sample}.txt', sample=samples[:2]),
            rest_files=expand('sample_{sample}.txt', sample=samples[2:])
        output: 'merged.txt'
        shell:
            'paste {input.first_files} > {output} \n'
            'for file in {input.rest_files} ; do '
                'paste {output} $file > {output}_tmp \n'
                'mv {output}_tmp {output} \n'
            'done '
    

    递归解

    递归解决方案背后的总体思路是合并文件 1 和 2、3 和 4、5 和 6,...在第一步中,它们合并这些中间文件,直到留下一个合并文件。难点在于snakemake自上而下对dag求值,文件数不一定能被2整除。

    rule merge:
        """Request final output from merged files 0 to N-1."""
        input:
            f'temp_0_{len(samples)-1}'
        output: 'merged.txt'
        shell:
            'cp {input} {output}'
    
    def merge_intermediate_input(wildcards):
        """From start and end indices, request input files.  Raises ValueError when indices are equal."""
        start, end = int(wildcards.start), int(wildcards.end)
        if start == end:  # perform link instead
            raise ValueError
        if start + 1 == end:  # base case
            return expand('sample_{sample}.txt',
                    sample=(samples[start], samples[end]))
        # default
        return [f'temp_{start}_{(start+end)//2}', f'temp_{(start+end)//2+1}_{end}']
    
    rule merge_intermediate:
        """Solve subproblem, producing start to end."""
        input: merge_intermediate_input
        output: temp('temp_{start}_{end}')
        shell:
            'paste {input} > {output}'
    
    def merge_base_input(wildcards):
        """Get input sample from index in list."""
        index = int(wildcards.start)
        return f'sample_{samples[index]}.txt'
    
    rule merge_base:
        """Create temporary symbolic link for input file with start==end."""
        input: merge_base_input
        output: temp('temp_{start}_{start}')
        shell:
            'ln -sr {input} {output}'
    

    merge_intermediate 解决了从中间拆分的两个合并文件生成从startend 的合并文件的子问题。当start == end 时,将合并文件创建为符号链接。当start + 1 == end 时,基本情况是在这些索引处合并输入文件。递归解决方案显然代码更多、更复杂,但在长时间运行或复杂的合并操作中效率更高。

    运行时复杂性、性能

    让N个文件每个有k行,合并操作的运行时复杂度有O(f(n))。在顺序解决方案中,临时文件被创建N-1次,其长度增加为2k,3k ...,总共k*N*(N+1)/2 - k ~ O(f(k N^2))

    对于递归解决方案,在第一层中,每对文件都被连接起来。每个操作都需要O(f(2k)) 并且有N/2 这样的操作。接下来,将每对结果文件合并,成本为O(f(4k))N/4 操作。总体而言,需要ln(N) 层合并才能产生最终输出,同样需要 N-1 次合并操作。整个操作的复杂度为O(f(k N ln(n)))

    就开销而言,递归解决方案会启动 N-1 个 snakemake 作业以及对调度程序、激活环境等的任何关联调用。顺序版本会启动单个作业并在单个 shell 进程中运行所有内容。

    递归解决方案可以以更高的并行度运行;递归解决方案的每个“级别”都是独立的,最多允许一次运行 N/2 个作业。顺序解决方案需要每个先前步骤的结果。递归解决方案的资源估计还有一个额外的挑战。第一个合并有O(2k),而最后一个合并有O(k N)。可以动态估计资源,或者,如果合并步骤不会增加生成的文件大小(例如相交区域),则资源可能是相似的。

    结论

    虽然递归解决方案提供了更好的渐近运行时复杂性,但它引入了更多的蛇形作业、临时文件和复杂的逻辑。顺序解决方案是直截了当的并且包含在单个作业中,但可能会慢N/ln(N) 倍。使用顺序解决方案可以成功执行快速合并操作,并且在 N 很大之前,运行时间不会差很多。但是,如果合并需要 10 分钟或更长时间,取决于输入文件的大小,并且生成的输出比输入长(例如 cat、paste 等),则递归解决方案可能会提供更好的性能和更短的挂钟时间。

    【讨论】:

      猜你喜欢
      • 2010-11-03
      • 2018-03-29
      • 1970-01-01
      • 2011-11-18
      • 2011-10-26
      • 2014-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多