如何加速这个Python代码?

时间:2010-11-17 22:24:04

标签: java python performance jvm jython

我有以下微小的Python方法 到目前为止 性能热点(根据我的分析器,>这里花费了95%的执行时间)在一个更大的计划中:

def topScore(self, seq):
    ret = -1e9999
    logProbs = self.logProbs  # save indirection
    l = len(logProbs)
    for i in xrange(len(seq) - l + 1):
        score = 0.0
        for j in xrange(l):
            score += logProbs[j][seq[j + i]]
        ret = max(ret, score)

    return ret

代码正在Python的Jython实现中运行,而不是CPython,如果这很重要的话。 seq是一个DNA序列字符串,大约有1,000个元素。 logProbs是一个词典列表,每个位置一个。目标是找到l的任何长度seq(大约10-20个元素)的最大分数。

我意识到所有这些循环由于解释开销而效率低下,并且在静态编译/ JIT语言中会快得多。但是,我不愿意切换语言。首先,我需要一个JVM语言用于我正在使用的库,这种约束我的选择。其次,我不想将此代码批量转换为较低级别的JVM语言。但是,如果有必要,我愿意用其他东西重写这个热点,虽然我不知道如何连接它或者开销会是什么。

除了这种方法的单线程缓慢之外,我还无法让程序在并行化方面超过4个CPU。鉴于它几乎把所有时间都花在我发布的10行热点上,我无法弄清楚这里的瓶颈是什么。

8 个答案:

答案 0 :(得分:2)

它慢的原因是因为它是O(N * N)

maximum subsequence算法可以帮助您改进此

答案 1 :(得分:2)

如果为topScore重复调用seq,则可以memoize其值{{1}}。

E.g。 http://code.activestate.com/recipes/52201/

答案 2 :(得分:1)

我不知道我在做什么,但也许这有助于加快你的算法:

ret = -1e9999
logProbs = self.logProbs  # save indirection
l = len(logProbs)

scores = collections.defaultdict(int)

for j in xrange(l):
    prob = logProbs[j]
    for i in xrange(len(seq) - l + 1):
        scores[i] += prob[seq[j + i]]


ret = max(ret, max(scores.values()))

答案 3 :(得分:1)

如何在for i循环之外预先计算xrange(l)

答案 4 :(得分:0)

没有什么能像慢一样跳出来。我可能会像这样重写内部循环:

score = sum(logProbs[j][seq[j+i]] for j in xrange(l))

甚至:

seqmatch = zip(seq[i:i+l], logProbs)
score = sum(posscores[base] for base, posscores in seqmatch)

但我不知道要么节省很多时间。

将DNA碱基存储为整数0-3可能稍微快一些,并从元组而不是字典中查找分数。将字母翻译成数字会有性能影响,但只需要进行一次。

答案 5 :(得分:0)

绝对使用numpy并将logProbs存储为2D数组而不是字典列表。如上所述,还将seq存储为(短)整数的一维数组。如果您不必在每次调用函数时都进行这些转换,这将有所帮助(在函数内部进行这些转换不会为您节省太多)。你可以消除第二个循环:

import numpy as np
...
print np.shape(self.logProbs) # (20, 4)
print np.shape(seq) # (1000,)
...
def topScore(self, seq):
ret = -1e9999
logProbs = self.logProbs  # save indirection
l = len(logProbs)
for i in xrange(len(seq) - l + 1):
    score = np.sum(logProbs[:,seq[i:i+l]])
    ret = max(ret, score)

return ret

之后您要做的事情取决于这两个数据元素中哪一个变化最频繁:

如果logProbs通常保持不变并且您希望通过它运行许多DNA序列,那么请考虑将DNA序列堆叠为2D阵列。 numpy可以非常快速地遍历2D阵列,所以如果你要处理200个DNA序列,它只需要比单个序列长一点。

最后,如果你真的需要加速,请使用scipy.weave。这是编写几行快速C以加速循环的一种非常简单的方法。但是,我建议使用scipy> 0.8。

答案 6 :(得分:0)

你可以尝试在循环之外提升不仅仅是self.logProbs:

def topScore(self, seq):
    ret = -1e9999
    logProbs = self.logProbs  # save indirection
    l = len(logProbs)
    lrange = range(l)
    for i in xrange(len(seq) - l + 1):
        score = 0.0
        for j in lrange:
            score += logProbs[j][seq[j + i]]
        if score > ret: ret = score # avoid lookup and function call

    return ret

答案 7 :(得分:0)

我怀疑它会产生重大影响,但你可以尝试改变:

  for j in xrange(l):
        score += logProbs[j][seq[j + i]]

  for j,lP in enumerate(logProbs):
        score += lP[seq[j + i]]

甚至在seq循环之外提升枚举。