Рубрики
Без рубрики

Обзор инструментов профилирования для Python

Получите практические, реальные навыки Python на наших ресурсах и пути

Автор оригинала: Mike Driscoll.

Что это значит для профиля кода? Основная идея бенчмаркинга или профилирования – выяснить, насколько быстро ваш код выполняется и где есть узкие места. Основная причина сделать такую вещь для оптимизации. Вы столкнетесь с ситуациями, когда вам нужен ваш код, чтобы запустить быстрее, потому что ваши потребности бизнеса изменились. Когда это произойдет, вам нужно будет выяснить, какие части вашего кода замедляются.

Эта статья будет охватывать только, как профилировать код с использованием различных инструментов. Это не будет на самом деле оптимизировать ваш код. Давайте начнем!

Использование Timeit.

Python поставляется с модулем под названием Время течения Отказ Вы можете использовать его во время небольших фрагментов кода. Время течения Модуль использует специфичные платформы времени, чтобы вы получили наиболее точные сроки возможными.

Модуль Timeit имеет интерфейс командной строки, но он также может быть импортирован. Мы начнем, посмотрев, как использовать Timeit из командной строки. Откройте терминал и попробуйте следующие примеры:

python -m timeit -s "[ord(x) for x in 'abcdfghi']"
100000000 loops, best of 3: 0.0115 usec per loop

python -m timeit -s "[chr(int(x)) for x in '123456789']"
100000000 loops, best of 3: 0.0119 usec per loop

Что тут происходит? Что ж, когда вы называете Python в командной строке и пропустите опцию «-M», вы говорите об этом, чтобы посмотреть модуль и использовать его в качестве основной программы. «-S» сообщает модуль Timeit, чтобы запустить настройку один раз. Затем он запускает код для N количество циклов 3 раза 3 раза и возвращает наилучшее среднее значение из 3 прогонов. Для этих глупых примеров вы не увидите большую разницу.

Ваш вывод, скорее всего, будет немного отличаться, так как он зависит от спецификаций вашего компьютера.

Давайте напишем глупое функцию и посмотрите, сможете ли вы время от командной строки:

# simple_func.py
def my_function():
    try:
        1 / 0
    except ZeroDivisionError:
        pass

Все эта функция делает, вызывает ошибку, которая быстро игнорируется. Да, это еще один глупый пример. Чтобы получить timeit, чтобы запустить этот код в командной строке, вам нужно будет импортировать код в его пространство имен, поэтому убедитесь, что вы изменили свой текущий рабочий каталог, чтобы быть в той же папке, в которой находится этот сценарий. Затем выполните следующее:

python -m timeit "import simple_func; simple_func.my_function()"
1000000 loops, best of 3: 1.77 usec per loop

Здесь вы импортируете функцию, а затем вызовите ее. Обратите внимание, что мы разделяем импорт и функцию вызова функции с полутоками, а код Python в кавычках. Теперь мы готовы узнать, как использовать Timeit внутри фактического сценария Python.

Импорт времени тестирования

Использование модуля Timeit внутри вашего кода также довольно легко. Вы будете использовать тот же самый глупый скрипт до и показать вам, как ниже:

# simple_func2.py
def my_function():
    try:
        1 / 0
    except ZeroDivisionError:
        pass
 
if __name__ == "__main__":
    import timeit
    setup = "from __main__ import my_function"
    print(timeit.timeit("my_function()", setup=setup))

Здесь вы проверяете, работает ли сценарий напрямую (то есть не импортирован). Если это так, то вы импортируете Время течения , создайте строку настройки, чтобы импортировать функцию в пространство имен Timeit, а затем мы звоним TimeIt.Timeit Отказ Вы отметите, что мы передаем вызов функции в кавычках, а затем строку установки. И это действительно все, что есть к этому! Теперь давайте узнаем о том, как написать наш собственный тип Timer.

Используйте декоратор

Написание собственного таймера тоже очень весело, хотя оно может быть не так точнее, как просто использует Timeit в зависимости от случая использования. Независимо от того, вы собираетесь написать собственную собственную функцию Timing Decorator!

Вот код:

import random
import time

def timerfunc(func):
    """
    A timer decorator
    """
    def function_timer(*args, **kwargs):
        """
        A nested function for timing other functions
        """
        start = time.time()
        value = func(*args, **kwargs)
        end = time.time()
        runtime = end - start
        msg = "The runtime for {func} took {time} seconds to complete"
        print(msg.format(func=func.__name__,
                         time=runtime))
        return value
    return function_timer


@timerfunc
def long_runner():
    for x in range(5):
        sleep_time = random.choice(range(1,5))
        time.sleep(sleep_time)

if __name__ == '__main__':
    long_runner()

Для этого примера вы импортируете Случайные и время Модули из стандартной библиотеки Python. Затем вы создаете нашу функцию декоратора. Вы заметите, что он принимает функцию и имеет другую функцию внутри этого. Вложенная функция будет схватить время перед вызовом прохожденной функции. Затем он ждет функции, чтобы вернуться и захватить время окончания. Теперь вы знаете, как долго работает функция, чтобы запустить, поэтому вы распечатаете его. Конечно, декоратор также должен вернуть результат вызова функции и самой функции, так что это все последние два утверждения.

Следующая функция украшена нашим декоратором Timing. Вы отметите, что он использует случайные для «случайным образом» спать несколько секунд. Это просто для демонстрации длительной программы. Вы фактически захотете функции времени, которые подключаются к базам данных (или запустить большие запросы), веб-сайты, запустить потоки или делать другие вещи, которые занимают время для завершения.

Каждый раз, когда вы запускаете этот код, результат будет немного отличаться. Дайте это попробовать и посмотрите сами!

Создать менеджер контекста Timing

Некоторые программисты любят использовать контекстные менеджеры для нескольких небольших кусочков кода. Так что давайте создадим наш собственный класс менеджера контекста таймера!

import random
import time

class MyTimer():

    def __init__(self):
        self.start = time.time()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        end = time.time()
        runtime = end - self.start
        msg = 'The function took {time} seconds to complete'
        print(msg.format(time=runtime))


def long_runner():
    for x in range(5):
        sleep_time = random.choice(range(1,5))
        time.sleep(sleep_time)


if __name__ == '__main__':
    with MyTimer():
        long_runner()

В этом примере мы используем класс __init__ Способ начать наш таймер. __enter__ Метод не должен делать ничего другого, а затем вернуть себе. Наконец, __exit__ Метод имеет все сочные биты. Здесь мы схватием время окончания, рассчитайте общее время выполнения и распечатайте его.

Конец кода на самом деле показывает пример использования нашего контекстно-менеджера, где мы обертываем функцию из предыдущего примера в нашем пользовательском менеджере контекста.

Используя cprofile

Python поставляется со встроенным собственным профилировщиком кода. Есть Профиль модуль и CPROFILE модуль. Модуль профиля является чистым python, но он добавит много накладных отложений в любой профиль, поэтому обычно рекомендуется выходить с Cprofile, который имеет аналогичный интерфейс, но намного быстрее.

Мы не собираемся войти в подробную информацию об этом модуле в этой статье, но давайте посмотрим на пару веселых примеров, чтобы вы получили вкус к тому, что он может сделать.

>>> import cProfile
>>> cProfile.run("[x for x in range(1500)]")
         4 function calls in 0.001 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 :1()
        1    0.000    0.000    0.000    0.000 :1()
        1    0.001    0.001    0.001    0.001 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

Давайте немного сломаемся. Первая строка показывает, что было 4 функциональных вызовов. Следующая строка говорит нам о том, как приведены результаты. Согласно документации, стандартное имя относится к дальнему правой колонке. Здесь есть несколько столбцов.

  • NCalls это количество вызовов.
  • tottime в целом времени, проведенного в данной функции.
  • Percall Относится к ценному титулю TOTTIME, разделенным на NCalls
  • CumTime Совокупное время, проведенное в этом и все подфункции. Это даже точно для рекурсивных функций!
  • Второй Percall столбец является частным кромка CumTime, разделенным на примитивные звонки
  • Имя имена: Lineno (Функция) Предоставляет соответствующие данные каждой функции

Вы можете вызвать CPROFIL в командной строке почти так же, как и с модулем Timeit. Основное отличие состоит в том, что вы передадите его сценарий Python вместо того, чтобы просто пропустить фрагмент. Вот пример вызова:

python -m cProfile test.py

Дайте ему попробовать один из ваших собственных модулей или попробуйте на одном из модулей Python, чтобы увидеть, как он работает.

Line_Profiler

Там аккуратный 3-й партийный проект под названием Line_Profiler Это предназначено для профиля времени, когда каждая отдельная линия принимает для выполнения. Это также включает в себя скрипт под названием Kernprof Для профилирования приложений Python и скриптов с помощью Line_Profiler. Просто используйте Пип Чтобы установить пакет. Вот как:

pip install line_profiler

На самом деле использовать Line_Profiler Нам понадобится какой-код код в профиль. Но сначала мне нужно объяснить, как работает Line_Profiler, когда вы называете его в командной строке. Вы на самом деле будете звонить Line_Profiler, позвонив скрипт KernProf. Я думал, что это было немного запутано в первый раз, когда я использовал его, но это просто так, как он работает.

Вот нормальный способ использовать его:

kernprof -l silly_functions.py

Это распечатает следующее сообщение, когда он закончит: Написал результаты профиля в Sylly_functions.py.lprof Отказ Это двоичный файл, который вы не можете просматривать напрямую. Когда вы запустите Kernprof Хотя это на самом деле будет ввести экземпляр LineProfiler в ваш сценарий __Builtins__ пространство имен. Экземпляр будет назван Профиль и предназначен для использования в качестве декоратора.

С учетом этого вы можете написать свой скрипт:

# silly_functions.py
import time

@profile
def fast_function():
    print("I'm a fast function!")

@profile
def slow_function():
    time.sleep(2)
    print("I'm a slow function")

if __name__ == '__main__':
    fast_function()
    slow_function()

Итак, теперь у вас есть две оформленные функции, которые оформлены с чем-то, что не импортируется. Если вы на самом деле пытаетесь запустить этот скрипт, как есть, вы получите NameError Потому что «профиль» не определен. Так что всегда не забудьте удалить свои декораторы после того, как вы профилировали свой код!

Давайте вернемся и научитесь на самом деле просмотреть результаты нашего профилирования. Есть два метода, которые мы можем использовать. Первый – использовать модуль Line_Profiler, чтобы прочитать наш файл результатов:

python -m line_profiler silly_functions.py.lprof

Альтернативный метод – просто использовать KernProf в Verbose Mode, пройдя -V :

kernprof -l -v silly_functions.py

Независимо от того, какой метод, который вы используете, вы должны заканчивать что-то вроде следующего, напечатано на ваш экран:

I'm a fast function!
I'm a slow function
Wrote profile results to silly_functions.py.lprof
Timer unit: 1e-06 s

Total time: 3.4e-05 s
File: silly_functions.py
Function: fast_function at line 3

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     3                                           @profile
     4                                           def fast_function():
     5         1           34     34.0    100.0      print("I'm a fast function!")

Total time: 2.001 s
File: silly_functions.py
Function: slow_function at line 7

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     7                                           @profile
     8                                           def slow_function():
     9         1      2000942 2000942.0    100.0      time.sleep(2)
    10         1           59     59.0      0.0      print("I'm a slow function")

Исходный код напечатан с информацией о времени для каждой строки. Здесь есть шесть колонн информации. Давайте узнаем, что каждый значит.

  • Линия # – строка номер кода, который был профилирован
  • Хиты – количество раз, когда конкретная линия была выполнена
  • Время – Общее количество времени, которую линия взяла, чтобы выполнить (в единице таймера). Блок таймера можно увидеть в начале вывода
  • За удар – среднее количество времени, когда строка кода заняла выполнение (в единицах таймера)
  • % Времени – процент времени, проведенного на линии относительно общего количества времени, проведенного в указанной функции
  • Содержание линии – фактический исходный код, который был выполнен

Если вы случились пользователем iPython, вы можете узнать, что iPython имеет Magic Command ( % lprun ), что позволяет указывать функции для профиля и даже какого оператора для выполнения.

memory_profiler.

Другой большой 3-й пакет профилирования Party Profific memory_profiler Отказ memory_profiler Модуль может быть использован для мониторинга расхода памяти в процессе, или вы можете использовать его для линейного анализа расхода памяти вашего кода. Поскольку он не включен в Python, вам придется установить его. Вы можете использовать Пип за это:

pip install memory_profiler

Как только он установлен, нам нужен код, чтобы запустить его. memory_profiler На самом деле работает так же, как line_profiler в том, когда вы запускаете его, memory_profiler внесу себе пример в __Builtins__ Названный профиль, который вы должны использовать в качестве декоратора на функции, которую вы профилируете.

Вот простой пример:

# memo_prof.py 
@profile
def mem_func():
    lots_of_numbers = list(range(1500))
    x = ['letters'] * (5 ** 10)
    del lots_of_numbers
    return None

if __name__ == '__main__':
    mem_func()

В этом примере вы создаете список, содержащий 1500 целых чисел. Затем вы создаете список с 9765625 (от 5 до 10) экземпляров строки. Наконец, вы удалите первый список и вернемся. memory_profiler Нет другого сценария, который вам нужно запустить, чтобы сделать фактическое профилирование, как Line_Profiler сделал. Вместо этого вы можете просто запустить Python и использовать его Параметр в командной строке для загрузки модуля и запустить его на нашем скрипте:

python -m memory_profiler memo_prof.py 
Filename: memo_prof.py

Line #    Mem usage    Increment   Line Contents
================================================
     1   16.672 MiB    0.000 MiB   @profile
     2                             def mem_func():
     3   16.707 MiB    0.035 MiB       lots_of_numbers = list(range(1500))
     4   91.215 MiB   74.508 MiB       x = ['letters'] * (5 ** 10)
     5   91.215 MiB    0.000 MiB       del lots_of_numbers
     6   91.215 MiB    0.000 MiB       return None

Столбцы довольно явно объясняют на этот раз. У нас есть наши номера строки, а затем объем памяти, используемой после указанной линии. Далее у нас есть приростное поле, которое сообщает нам разницу в памяти текущей линии по сравнению с строкой предыдущей. Сам последний столбец предназначен для самого кода.

memory_profiler также включает в себя MPROF Что можно использовать для создания полной памяти отчетов об использовании во времени вместо строки. Это очень легко использовать; Просто посмотрите:

$ mprof run memo_prof.py 
mprof: Sampling memory every 0.1s
running as a Python program...

MPROF Также может создать график, который показывает, как ваше приложение потребляло память со временем. Чтобы получить график, все, что вам нужно сделать, это:

$ mprof plot

Для глупого примера мы создали ранее, я получил следующий график:

Вы должны попытаться запустить его самостоятельно против гораздо сложного примера, чтобы увидеть более интересный сюжет.

Профиль

Последний 3-й партийный пакет, на котором вы будете смотреть в этой статье, называется Профиларки Отказ Это коллекция декоративов, специально разработанных для функций профилирования. Чтобы установить Профиларки просто сделайте следующее:

pip install profilehooks

Теперь, когда мы его установили, давайте повторно используйте пример из последнего раздела и немного измените его, чтобы использовать Профиларки :

# profhooks.py
from profilehooks import profile


@profile
def mem_func():
    lots_of_numbers = list(range(1500))
    x = ['letters'] * (5 ** 10)
    del lots_of_numbers
    return None

if __name__ == '__main__':
    mem_func()

Все, что вам нужно сделать, чтобы использовать Профиларки Импортирует его, а затем украсить функцию, которую вы хотите профиль. Если вы запускаете код выше, вы получите выход, аналогичный следующему, отправленному на STDOUT:

Выход для этого пакета, по-видимому, следит за тем, как модуль Cprofile из стандартной библиотеки Python. Ранее вы можете обратиться к описаниям столбцов в этой главе, чтобы увидеть, что это означает. Профиларки Упаковка имеет еще два декоратора. Первый мы рассмотрим, называется TimeCall Отказ Это дает нам время выполнения курса функции:

# profhooks2.py
from profilehooks import timecall

@timecall
def mem_func():
    lots_of_numbers = list(range(1500))
    x = ['letters'] * (5 ** 10)
    del lots_of_numbers
    return None

if __name__ == '__main__':
    mem_func()

Когда вы запускаете этот кусок кода, вы увидите что-то похожее на следующий вывод:

mem_func (c:\path_to_script\profhooks2.py:3):
  0.141 seconds

Весь декоратор – это время время выполнения функции, но без накладных расходов профилирования. Это как использовать Время течения Отказ

Последний декоратор, который Профиларки предоставляет называется покрытие Отказ Предполагается распечатать охват линии одной функции. Я не на самом деле не нашел этого всего, что полезен, но вы можете дать ему попробовать самостоятельно.

Наконец я просто хочу упомянуть, что вы также можете запустить Профиларки На командной строке с помощью Python’s флаг:

python -m profilehooks mymodule.py

Пакет профилей довольно круто и имеет много потенциалов.

Обертывание

Вы охватывали много информации в этой статье. Вы узнали, как использовать встроенные модули Python, Время течения и CPROFILE Время и профиль ваш код соответственно. Вы также узнали, как написать свой собственный код времени и использовать его в качестве декоратора или менеджера контекста. Затем мы переехали и посмотрели на некоторые третий пакетные пакеты; а именно Line_Profiler , memory_profiler * и Профиларки Отказ На данный момент вы должны быть хорошо на пути, чтобы сравнить свой собственный код. Попробуйте посмотрите, сможете ли вы найти какие-либо узкие места самостоятельно.