Как ускорить этот код Python?

у меня есть следующий крошечный метод 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

код запускается в реализации Jython Python, а не CPython, если это имеет значение. seq - строка последовательности ДНК, порядка 1000 элементов. logProbs список словарей, по одному для каждой позиции. Цель состоит в том, чтобы найти максимум оценка любой длины l (порядка 10-20 элементов) подпоследовательность seq.

Я понимаю, что весь этот цикл неэффективен из-за накладных расходов на интерпретацию и будет намного быстрее на статически скомпилированном/JIT'D языке. Однако я не хочу менять язык. Во-первых, мне нужен язык JVM для библиотек, которые я использую, и это ограничивает мой выбор. Во-вторых, я не хочу переводить этот код оптом на язык JVM более низкого уровня. Тем не менее, я готов переписать эту точку доступа в чем-то другом, если это необходимо, хотя я понятия не имею, как ее подключить или какие накладные расходы будут.

в дополнение к однопоточной медлительности этого метода, я также не могу заставить программу масштабировать намного больше 4 процессоров с точки зрения распараллеливания. Учитывая, что он проводит почти все свое время в 10-линейной точке доступа, которую я разместил, я не могу понять, какое узкое место может быть здесь.

8 ответов


Если topScore вызывается повторно для того же seq вы можете memoize его стоимости.

например. http://code.activestate.com/recipes/52201/


причина медленная, потому что это O (N*N)

на максимальная подпоследовательность алгоритм может помочь вам улучшить это


насчет препроцесса xrange(l) вне цикла for i?


Я понятия не имею, что я делаю, но, возможно, это может помочь ускорить ваш алгоритм:

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

ничто не выскакивает как медленное. Я мог бы переписать внутренний цикл следующим образом:

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)

но я не знаю, что это сэкономит много времени.

было бы немного быстрее хранить базы ДНК в виде целых чисел 0-3 и искать оценки из кортежа вместо словаря. Будет хит производительности по переводу букв в цифры, но это нужно сделать только один раз.


определенно используйте numpy и храните logProbs как 2D-массив вместо списка словарей. Также храните seq как 1D массив (коротких) целых чисел, как предложено выше. Это поможет, если вам не нужно делать эти преобразования каждый раз, когда вы вызываете функцию (выполнение этих преобразований внутри функции не сэкономит вам много). Вы можете их устранить второй цикл:

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

то, что вы делаете после этого, зависит от того, какой из этих 2 элементов данных изменяется больше всего часто:

Если logProbs обычно остается неизменным, и вы хотите запустить через него много последовательностей ДНК, то рассмотрите возможность укладки последовательностей ДНК в виде 2D-массива. numpy может проходить через 2D-массив очень быстро, поэтому, если у вас есть 200 последовательностей ДНК для обработки, это займет немного больше времени, чем один.

наконец, если вам действительно нужно ускорить, используйте составляющей.ткать. Это очень простой способ написать несколько строк быстрого C, чтобы ускорить вас петли. Тем не менее, я рекомендую scipy >0.8.


вы можете попробовать поднять больше, чем просто себя.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

Я сомневаюсь, что это будет иметь существенное значение, но вы можете попробовать изменить:

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

to

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

или даже поднятие этого перечисления вне цикла seq.