【问题标题】:How to speed up this Python code?如何加速这个 Python 代码?
【发布时间】:2010-11-17 22:24:03
【问题描述】:

我有以下小型 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 是一个字典列表,每个位置一个。目标是找到seq的任意长度l(大约10-20个元素)子序列的最高分。

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

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

【问题讨论】:

  • 我无法完全理解您正在使用的数据结构。你能发布一个简短的seqlogProbs 示例吗?
  • 我的第一个想法是 numpy,所以也许这个页面上的一些东西可能有用:stackoverflow.com/questions/316410/…
  • 我的第二个想法是改变迭代,这样你只检查 seq 一次,但这可能意味着 logProbs 和 score 变得更加复杂,并且实际上可能不会减少完成的工作。
  • @Russell:Jython 中没有 numpy,但我认为您应该能够访问 Java 的数字。
  • @Fred Larson:抱歉,我的意思是让logProbs 列表中的每个项目都成为列表而不是字典。我相信seq[index] 可以拥有的可能值很少。这需要在逻辑上将每个可能的值映射到索引,但这可能比从序列中散列每个值以查找其值(如果它是字典)更快。

标签: java python performance jvm jython


【解决方案1】:

如果topScore 被重复调用相同的seq 你可以memoize 它的值。

例如http://code.activestate.com/recipes/52201/

【讨论】:

  • 虽然我希望从这篇文章中得到一些更深入的信息,但我会接受这一点,因为这是我最终所做的。
  • 这个秘籍基本上展示了如何编写一个装饰器来捕获返回值并将其映射到输入。我想我会给你代码来做到这一点,因为我总是喜欢使用其他人的代码而不是自己编写
【解决方案2】:

之所以慢是因为它是O(N*N)

maximum subsequence 算法可以帮助您改进这一点

【讨论】:

  • 这个问题与最大子序列有点不同,刚好足以使建议的解决方案不太奏效。
【解决方案3】:

在 for i 循环之外预计算 xrange(l) 怎么样?

【讨论】:

    【解决方案4】:

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

    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()))
    

    【讨论】:

      【解决方案5】:

      没有什么是慢的。我可能会像这样重写内部循环:

      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 并从元组而不是字典中查找分数可能会稍微快一些。将字母转换为数字会影响性能,但只需要做一次。

      【讨论】:

      • 如果准确性很重要,可能需要使用math.fsum()
      【解决方案6】:

      绝对使用 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。

      【讨论】:

        【解决方案7】:

        您可以尝试在循环之外提升不仅仅是 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
        

        【讨论】:

          【解决方案8】:

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

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

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

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-01-30
            • 1970-01-01
            • 2011-08-05
            • 2023-03-16
            • 1970-01-01
            • 2021-10-31
            • 2022-01-21
            相关资源
            最近更新 更多