Профилирование генераторов Python

Я адаптирую приложение, которое интенсивно использует генераторы для получения результатов, чтобы обеспечить веб-интерфейс web.py.

До сих пор я мог обернуть вызов цикла for и операторы, производящие вывод, в функцию и вызвать ее, используя cProfile.run() или runctx(). Концептуально:

def output():
    for value in generator():
        print(value)

cProfile.run('output()')

В web.py я должен обернуть его следующим образом, так как я хочу немедленно получить вывод из потенциально длительных вычислений на каждом шаге итерации, используя yield:

class index:
    def GET(self):
        for value in generator():
            yield make_pretty_html(value)

Есть ли способ профилировать все вызовы генератора, как в первом примере, когда он используется, как во втором?


person Daniel Beck    schedule 25.08.2010    source источник
comment
Вы просто хотите измерить весь вызов функции, а не только одну итерацию? Как в cProfile.run('list(index().GET())') ?   -  person Jochen Ritzel    schedule 10.09.2010
comment
По сути, это то, что выполняет цикл for. Проблема здесь в том, что я не могу контролировать вызовы GET(), их обрабатывает web.py. Кроме того, я не думаю, что вывод будет производиться таким образом (с использованием возвращаемого значения).   -  person Daniel Beck    schedule 10.09.2010


Ответы (3)


Я наконец нашел решение. Возвращайте значение профилирования через здесь.

import cProfile
import pstats
import glob
import math

def gen():
    for i in range(1, 10):
        yield math.factorial(i)

class index(object):
    def GET(self):
        p = cProfile.Profile()

        it = gen()
        while True:
            try:
                nxt = p.runcall(next, it)
            except StopIteration:
                break
            print nxt

        p.print_stats()

index().GET()

Я также мог бы объединить несколько таких результатов профилирования (как только я начну давать уникальные имена файлам) через документацию и хранить/анализировать их вместе.

person Daniel Beck    schedule 10.09.2010

Похоже, вы пытаетесь профилировать каждый вызов «далее» в генераторе? Если это так, вы можете обернуть свой генератор в генератор профилирования. Что-то вроде этого, где закомментированная часть будет отправлять результаты в журнал или базу данных.

def iter_profiler(itr):
  itr = iter(itr)
  while True:
    try:
      start = time.time()
      value = itr.next()
      end = time.time()
    except StopIteration:
      break
    # do something with (end - stop) times here
    yield value

Затем вместо создания экземпляра вашего генератора как generator() вы должны использовать iter_profiler(generator())

person Community    schedule 10.09.2010
comment
в качестве альтернативы вы можете применить его модифицированную версию в качестве декоратора при определении генератора. - person aaronasterling; 10.09.2010
comment
В принципе, ты прав. Я уже считаю миллисекунды между до и после цикла for во втором примере моего исходного сообщения, поэтому у меня уже есть эта информация (хотя я считаю немного больше, чем вы, но несколько миллисекунд не имеют значения). Однако меня больше волнуют горячие точки в моем коде (куда уходит время вычислений?), чем то, какой именно из десятков результатов занял больше времени, чем другие. Поэтому я надеялся на решение на основе profile/cProfile (которое не генерирует один отчет для каждого результата). Если есть способ объединить отчеты профилировщика, это будет путь. - person Daniel Beck; 10.09.2010
comment
Для тех, кто ищет это решение в python3.x, обратите внимание, что у генераторов больше нет метода next(), используйте бесплатную функцию (stackoverflow.com/questions/21622193/). Это сделано в приведенном выше ответе. - person juicedatom; 14.08.2020

Можете ли вы просто использовать time.time() для профилирования интересующих вас частей? Просто получите текущее время и вычтите из последнего измерения.

person karpathy    schedule 25.08.2010
comment
Я уже получил общее время, но обработка заняла 5,382 секунды, что недостаточно для поиска узких мест в производительности. Я использую довольно большую и разветвленную цепочку генераторов для внутреннего использования и предназначен для хранения пользовательского ввода и итоговой производительности для последующего анализа. У меня есть несколько функций, каждый вызов которых занимает в среднем 0,000 секунды, но их можно вызывать десятки тысяч раз. - person Daniel Beck; 26.08.2010
comment
В этом случае вы можете иметь целочисленный счетчик для каждого выполнения этих функций и выполнять измерение только при каждом 1000-м выполнении? Вместо этого вы можете измерять куски кода таким образом и сужать узкие места. Я понимаю, что это может быть немного утомительно, в зависимости от кода. - person karpathy; 26.08.2010