如何在python(来自FASTA的GC计算器)中并行化此代码?

时间:2017-01-23 22:04:11

标签: python python-multiprocessing dna-sequence

System Monitor during process在编程时我是新手。我已经完成了生物学实用计算这本书,并且正在玩一些稍微更高级的概念。

我编写了一个Python(2.7)脚本,该脚本读入.fasta文件并计算GC内容。代码如下所示。

我正在使用的文件很麻烦(~3.9 Gb),我想知道是否有办法利用多个处理器,或者它是否值得。我有一个四核(超线程)Intel i-7 2600K处理器。

我运行代码并查看系统资源(附图)以查看CPU上的负载是什么。这个过程CPU有限吗? IO有限吗?这些概念对我来说很新鲜。我玩过多处理模块和Pool(),但没有用(可能是因为我的函数返回了一个元组)。

以下是代码:

def GC_calc(InFile):
    Iteration = 0
    GC = 0
    Total = 0
    for Line in InFile:
        if Line[0] != ">":
            GC = GC + Line.count('G') + Line.count('C')
            Total = Total + len(Line)
            Iteration = Iteration + 1
            print Iteration
    GCC = 100 * GC / Total
    return (GC, Total, GCC)


InFileName = "WS_Genome_v1.fasta"
InFile = open(InFileName, 'r')
results = GC_calc(InFile)
print results

3 个答案:

答案 0 :(得分:0)

目前,代码的主要瓶颈是print Iteration。打印到stdout真的很慢。如果你删除这一行,我预计会有一个重大的性能提升,或者至少,如果你绝对需要它,将它移动到另一个线程。但是,线程管理是一个高级主题,我建议不要立即进入。

另一个可能的瓶颈是您从文件中读取数据。文件IO可能很慢,特别是如果您的计算机上只有一个HDD。使用单个硬盘驱动器,您根本不需要使用多处理,因为您无法为处理器核心提供足够的数据。面向性能的RAID和SSD可以在这里提供帮助。

最后的评论是尝试使用grep和类似的文本操作程序而不是python。他们经历了数十年的优化,并且有更好的工作方式。关于SO的问题很多,其中grep优于python。或者至少可以在将数据传递给脚本之前filter out FASTA标头:

$ grep "^[>]" WS_Genome_v1.fasta | python gc_calc.py

(从here取得grep "^[>]"。)在这种情况下,您不应该在脚本中打开文件,而是从sys.stdin读取行,就像现在这样做

答案 1 :(得分:0)

基本上,您计算每行中CG的数量,并计算行的长度。 只有在最后你计算总数。

这样的过程很容易并行进行,因为每行的计算都是独立的。

假设计算是在CPython(来自python.org的那个)中完成的,threading由于GIL而无法提高性能。

这些计算可以与multiprocessing.Pool并行完成。 进程不像线程那样共享数据。而且我们不想将3.9 GB文件的一部分发送给每个工作进程! 因此,您希望每个工作进程自己打开文件。操作系统的缓存应该注意来自同一文件的页面不会多次加载到内存中。

如果你有N个核心,我会创建一个worker函数,以便处理每个第N行,并带有一个偏移量。

def worker(arguments):
    n = os.cpu_count() + 1
    infile, offset = arguments
    with open(infile) as f:
        cg = 0
        totlen = 0
        count = 1
        for line in f:
            if (count % n) - offset == 0:
                if not line.startswith('>'):
                    cg += line.count('C') +
                          line.count('G')
                    totlen += len(line)
            count += 1
    return (cg, totlen)

你可以像这样运行游泳池;

import multiprocessing as mp
from os import cpu_count

pool = mp.Pool()
results = pool.map(worker, [('infile', n) for n in range(1, cpu_count()+1)])

默认情况下,Pool创建的工作数与CPU具有核心数相同。

results将是(cg,len)元组的列表,您可以很容易地求和。

修改更新以修复模零错误。

答案 2 :(得分:0)

我现在有一个并行化的工作代码(特别感谢@Roland Smith)。我只需要对代码进行两次小修改,并且对于.fasta文件的结构有一个警告。最终(工作)代码如下:

###ONLY WORKS WHEN THERE ARE NO BREAKS IN SEQUENCE LINES###

def GC_calc(arguments):
    n = mp.cpu_count()
    InFile, offset = arguments
    with open(InFile) as f:
        GC    = 0
        Total = 0
        count = 0
        for Line in f:
            if (count % n) - offset == 0:
                if Line[0] != ">":
                    Line = Line.strip('\n')
                    GC += Line.count('G') + Line.count('C')
                    Total += len(Line)
            count += 1
    return (GC, Total)

import time
import multiprocessing as mp

startTime = time.time()
pool = mp.Pool()
results = pool.map(GC_calc, [('WS_Genome_v2.fasta', n) for n in range(1, mp.cpu_count()+1)])
endTime = time.time()
workTime = endTime - startTime
#Takes the tuples, parses them out, adds them
GC_List = []
Tot_List = []
# x = GC count, y = total count: results = [(x,y), (x,y), (x,y),...(x,y)]
for x,y in results:
    GC_List.append(x)
    Tot_List.append(y)

GC_Final = sum(GC_List)
Tot_Final = sum(Tot_List)

GCC = 100*float(GC_Final)/float(Tot_Final)

print results
print
print "Number GC = ", GC_Final
print "Total bp = ", Tot_Final
print "GC Content = %.3f%%" % (GCC)
print
endTime = time.time()
workTime = endTime - startTime
print "The job took %.5f seconds to complete" % (workTime)

需要注意的是.fasta文件在序列本身中不能有中断。我的原始代码没有问题,但是当序列被分成多行时,这段代码将无法正常工作。这很简单,可以通过命令行修复。

我还必须在两个方面修改代码:

n = mp.cpu_count()

count = 0

最初,count设置为1,n设置为mp.cpu_count()+ 1。即使在文件更正后,这也会导致计数不准确。缺点是它还允许所有8个核心(井,线程)工作。新代码只允许4在任何给定时间工作。

但是DID将这个过程从大约23秒加速到大约13秒!所以我说这是成功的(除了纠正原始.fasta文件所花费的时间)。