Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/321.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
使用pysam.TabixFile注释读取的python脚本中的处理速度 第一个问题_Python_Performance_Python 3.x_Bioinformatics_Samtools - Fatal编程技术网

使用pysam.TabixFile注释读取的python脚本中的处理速度 第一个问题

使用pysam.TabixFile注释读取的python脚本中的处理速度 第一个问题,python,performance,python-3.x,bioinformatics,samtools,Python,Performance,Python 3.x,Bioinformatics,Samtools,我正在用python(3.5)编写一个生物信息学脚本,它解析一个大的(排序和索引的)文件,表示基因组上对齐的序列读取,将基因组信息(“注释”)与这些读取关联起来,并统计遇到的注释类型。我正在测量我的脚本处理对齐读取的速度(超过1000次读取的批次),并获得以下速度变化: 什么可以解释这种模式? 我的直觉会让我打赌某些数据结构会随着密度的增加而逐渐变慢,但会不断扩展 不过,内存使用似乎并不重要(根据htop,在运行了近2个小时后,我的脚本仍然只使用了计算机内存的0.1%) 我的代码是如何工作的(

我正在用python(3.5)编写一个生物信息学脚本,它解析一个大的(排序和索引的)文件,表示基因组上对齐的序列读取,将基因组信息(“注释”)与这些读取关联起来,并统计遇到的注释类型。我正在测量我的脚本处理对齐读取的速度(超过1000次读取的批次),并获得以下速度变化:

什么可以解释这种模式?

我的直觉会让我打赌某些数据结构会随着密度的增加而逐渐变慢,但会不断扩展

不过,内存使用似乎并不重要(根据
htop
,在运行了近2个小时后,我的脚本仍然只使用了计算机内存的0.1%)

我的代码是如何工作的(实际代码见末尾)

我正在使用
pysam
模块进行bam文件解析。该方法为我提供了一个迭代器,以对象的形式提供有关连续对齐读取的信息

我根据它们的对齐坐标和格式(用bgzip压缩并用索引)的注释文件将注释与读取关联起来。我使用该方法(仍然来自
pysam
)来获取这些注释,我过滤它们并以字符串的
frozenset
形式生成它们的摘要(
process\u annotations
,下面未显示,返回这样一个
frozenset
),在一个生成器函数中,该函数在AlignedSegment迭代器上内部循环

我将生成的冻结集馈送到
计数器
对象。计数器是否对观察到的速度行为负责

我如何才能找到避免这些常规减速的方法?


附加测试 根据评论中的建议,我使用
cProfile
分析了我的整个分析,发现大部分运行时间都花在访问注释数据上(
pysam/ctabix.pyx:579(
cnext
,请参阅后面的调用图),如果我理解正确,这是一些Cython代码与SAMC库接口。观察到的减速原因似乎很难理解

为了加速我的脚本,我尝试了另一种基于
pybedtools
python接口和bedtools的解决方案,它还可以从gtf文件()中检索注释

速度 速度的提高非常重要。以下是实际的命令和计时结果(两者实际上是并行运行的):

$time python3-m cProfile-o tests/total_pybedtools.prof~/src/bioinfo_utils/small_RNA_seq_annotate.py-b results/bowtie2/mapped_C_elegans/WT_1_21-26_on_C_elegans.bam-g annotations/all_annotations.gtf-a“pybedtools”-total_pybedtools.log>total_pybedtools.out
real 5m48.474s
用户5m48.204s
系统0m1.336s
$time python3-m cProfile-o tests/total_tabix.prof~/src/bioinfo_utils/small_RNA_seq_annotate.py-b results/bowtie2/mapped_C_elegans/WT_1_21-26_on_C_elegans_sorted.bam-g annotations/all_annotations.gtf.gz-a“tabix”-l total_tabix.log>total_tabix.out
皇家195m40.990年代
用户194m54.356s
系统0m47.696s
(需要注意的是:两种方法的注释结果略有不同。也许我应该检查如何处理坐标。)

速度曲线没有先前观察到的长周期下降:

我的速度问题已经解决,但我仍然对基于tabix的方法为什么会出现这些速度下降的事后诸葛亮感兴趣。出于这个原因,我添加了“生物信息学”和“samtools”标签

调用图 作为记录,我在评测结果上使用gprof2dot生成了调用图:

$gprof2dot-f pstats测试/total_pybedtools.prof\
|dot-Tpng-o测试/total_pybedtools_callgraph.png
$gprof2dot-f pstats测试/总计\
|dot-Tpng-o测试/total_tabix_callgraph.png
以下是基于tabix方法的调用图:

对于基于pybedtools的方法:

代码 以下是我当前代码的主要部分:

@contextmanager
def annotation_context(annot_file, getter_type):
    """Yields a function to get annotations for an AlignedSegment."""
    if getter_type == "tabix":
        gtf_parser = pysam.ctabix.asGTF()
        gtf_file = pysam.TabixFile(annot_file, mode="r")
        fetch_annotations = gtf_file.fetch
        def get_annotations(ali):
            """Generates an annotation getter for *ali*."""
            return fetch_annotations(*ALI2POS_INFO(ali), parser=gtf_parser)
    elif getter_type == "pybedtools":
        gtf_file = open(annot_file, "r")
        # Does not work because somehow gets "consumed" after first usage
        #fetch_annotations = BedTool(gtf_file).all_hits
        # Much too slow
        #fetch_annotations = BedTool(gtf_file.readlines()).all_hits
        # https://daler.github.io/pybedtools/topical-low-level-ops.html
        fetch_annotations = BedTool(gtf_file).as_intervalfile().all_hits
        def get_annotations(ali):
            """Generates an annotation list for *ali*."""
            return fetch_annotations(Interval(*ALI2POS_INFO(ali)))
    else:
        raise NotImplementedError("%s not available" % getter_type)
    yield get_annotations
    gtf_file.close()

def main():
    """Main function of the program."""
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        "-b", "--bamfile",
        required=True,
        help="Sorted and indexed bam file containing the mapped reads."
        "A given read is expected to be aligned at only one location.")
    parser.add_argument(
        "-g", "--gtf",
        required=True,
        help="A sorted, bgzip-compressed gtf file."
        "A corresponding .tbi tabix index should exist.")
    parser.add_argument(
        "-a", "--annotation_getter",
        choices=["tabix", "pybedtools"],
        default="tabix",
        help="Method to use to access annotations from the gtf file.")
    parser.add_argument(
        "-l", "--logfile",
        help="File in which to write logs.")
    args = parser.parse_args()
    if not args.logfile:
        logfilename = "%s.log" % args.annotation_getter
    else:
        logfilename = args.logfile
    logging.basicConfig(
        filename=logfilename,
        level=logging.DEBUG)
    INFO = logging.info
    DEBUG = logging.debug
    WARNING = logging.warning
    process_annotations = make_annotation_processor(args.annotation_getter)
    with annotation_context(args.gtf, args.annotation_getter) as get_annotations:
        def generate_annotations(bamfile):
            """Generates annotations for the alignments in *bamfile*."""
            last_t = perf_counter()
            for i, ali in enumerate(bamfile.fetch(), start=1):
                if not i % 1000:
                    now = perf_counter()
                    INFO("%d alignments processed (%.0f alignments / s)" % (
                        i,
                        1000.0 / (now - last_t)))
                    #if not i % 50000:
                    #    gc.collect()
                    last_t = perf_counter()
                yield process_annotations(get_annotations(ali), ali)
        with pysam.AlignmentFile(args.bamfile, "rb") as bamfile:
            annot_stats = Counter(generate_annotations(bamfile))
            print(*reversed(annot_stats.most_common()), sep="\n")
    return 0

(我使用了contextmanager和其他高阶函数(
make_annotation_processor
和该函数调用的函数),使在同一脚本中使用各种注释检索方法变得更容易。)

无需进一步分析,我会想到垃圾收集器定期清理未使用的内存谢谢你的建议。我将尝试比观察到的振荡周期更频繁地运行
gc.collect()
,并查看速度模式是否发生变化。添加到列表中的是摊销
O(1)
。你还得付钱。可能尖峰是数据结构被复制和填充的地方,以便它们可以增长更多(这也将涉及垃圾收集)。关键动作似乎发生在未显示的模块中包含的代码中,因此这只是猜测工作。如果您知道需要多少空间,也许可以修改底层代码,以便预先分配所需的空间(使用列表很容易,我不知道如何使用字典)。我建议您分析脚本,看看它在哪里花费了大部分时间。请看,这可能会为您提供有关发生了什么的重要线索。每50000次读取运行一次
gc.collect()
(比观察到的振荡周期更频繁,大约100000次读取)不会改变速度曲线。