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

Профилирование кода Python с Py-Spy

Если у вас есть программа Python, которая в настоящее время работает, вы можете понять, что в реальном мире … Tagged с Python.

Если у вас есть программа Python, которая в настоящее время работает, вы можете понять, каково реальное профиль производительности кода. Эта программа может быть в производственной среде или просто на вашей местной машине. Вы захотите понять, где проведенная программа тратит свое время, и существуют ли какие -либо «горячие точки», которые следует исследовать для улучшения. Возможно, вы имеете дело с производственной системой, которая плохо себя ведет, и вы можете профилировать ее ненавязчивым способом, который не влияет на производительность производства или требует модификаций кода. Как это хороший способ сделать это? Эта статья расскажет о py-spy , инструмент, который позволяет вам профилировать программы Python, которые уже работают.

Детерминированные и профилировщики отбора проб

В более ранних статьях я написал о двух детерминированных профилирующих, cProfile и line_profiler Анкет Эти профилировщики полезны, когда вы разрабатываете код и хотите профилировать разделы кода или всего процесса. Поскольку они детерминированные, они будут точно сказывать вам, сколько раз выполняется функция (или в случае line_profiler , строка) и сколько времени она относительно берет, чтобы выполнить по сравнению с остальной частью вашего кода. Поскольку эти профилировщики работают в рамках наблюдаемого процесса, они несколько замедляют его, потому что им приходится делать бухгалтерский уст и расчет в разгар программы. Для производственного кода, изменение кода или перезапуск его с помощью включенного профилировщика часто не является вариантом.

Здесь могут быть полезные профилировщики отбора проб. Профилировщик отбора проб рассматривает существующий процесс и использует различные трюки, чтобы определить, что делает процесс выполнения. Вы можете вручную попробовать это самостоятельно. Например, на Linux вы можете использовать PSTACK (или gstack ) Команда, чтобы увидеть, что делает ваш процесс. На Mac вы можете выполнить Echo "Thread Backtrace All" | lldb -p увидеть что -то подобное. Вывод будет стопкой всех потоков в вашем процессе. Это работает для любого процесса, а не только для программ Python. Для ваших программ Python вы увидите основные функции C, а не ваши функции Python. В некоторых случаях проверка стека несколько раз таким образом может сказать вам, застрял ли ваш процесс или где он медленный, при условии, что вы можете сделать перевод в свой собственный код. Но это обеспечивает только один образец во времени. Поскольку процесс постоянно выполняется, ваш образец может меняться каждый раз, когда вы запускаете команду (если он не заблокирован или вам просто очень повезло).

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

пио- шпион

Py-Spy использует системные вызовы ( process_vm_readv on linux, vm_read on osx, readprocessmemory на Windows), чтобы получить стек вызовов, а затем переводит эту информацию в функцию Python, которые вы видите в Ваш исходный код. Он выбирает несколько раз в секунду, поэтому у него есть хорошие шансы увидеть вашу программу в различных состояниях, что она будет со временем. Это написано в ржавчине для скорости.

Получить Py-Spy в ваш проект очень просто, он можно установить через Pip Анкет Чтобы показать вам, как его использовать, я создал какой-то образец кода для профиля и наблюдения, как PY-Spy может рассказать нам о запущенном процессе Python. Если вы хотите следовать, вы можете легко воспроизвести эти шаги.

Сначала я настраиваю новую виртуальную среду, используя py-env и Pyenv-virtualenv plugin для этого проекта. Вы можете сделать это или настроить виртуальную среду, используя ваш предпочтительный инструмент.

# whichever Python version you prefer
pyenv install 3.8.7             
# make our virtualenv (with above version)
pyenv virtualenv 3.8.7 py-spy   
# activate it
pyenv activate py-spy           
# install py-spy
pip install py-spy              
# make sure we pick up the commands in our path
pyenv rehash                    

Это все, что нужно, у нас теперь есть инструменты. Если вы запускаете Py-Spy, вы можете увидеть общее использование.

$ py-spy
py-spy 0.3.4
Sampling profiler for Python programs

USAGE:
    py-spy 

OPTIONS:
    -h, --help Prints help information
    -V, --version Prints version information

SUBCOMMANDS:
    record Records stack trace information to a flamegraph, speedscope or raw file
    top Displays a top like view of functions consuming CPU
    dump Dumps stack traces for a target program to stdout
    help Prints this message or the help of the given subcommand(s)

Пример

Чтобы продемонстрировать Py-Spy, я написал простой продолжительный процесс, который будет потреблять цены на потоковую передачу от обмена криптовалютами и генерировать записи каждую минуту (это также известно как бар). Бар содержит различную информацию с последней минуты. Бар включает в себя высокую, низкую и последнюю цену, объем и средневзвешенную цену (VWAP). Прямо сейчас код регистрирует только эти значения, но может быть расширен для обновления базы данных. Хотя это просто, это полезный пример для использования, так как криптовалюты торгуются по часам и даст нам данные реального мира для работы.

Я использую Coinbase Pro Api для Python Чтобы получить доступ к данным из подачи WebSocket. Вот первый сокращение, в котором остался код отладки (вместе с двумя способами генерирования VWAP, один неэффективный ( _ VWAP Метод) и один более эффективный). Посмотрим, показывает ли Py-Spy, сколько времени использует этот код.

Этот код в конечном итоге генерирует поток для клиента WebSocket. Loop Asyncio установит таймер для границы следующей минуты, чтобы сообщить клиенту регистрировать данные панели. Он будет работать до тех пор, пока вы не убьете его (например, CTRL-C).

#!/usr/bin/env python

import argparse
import functools
import datetime
import asyncio
import logging

import arrow
import cbpro

class BarClient(cbpro.WebsocketClient):
    def __init__ (self, **kwargs):
        super(). __init__ (**kwargs)
        self._bar_volume = 0
        self._weighted_price = 0.0
        self._ticks = 0
        self._bar_high = None
        self._bar_low = None
        self.last_msg = {}

        self._pxs = []
        self._volumes = []

    def next_minute_delay(self):
        delay = (arrow.now().shift(minutes=1).floor('minutes') - arrow.now())
        return (delay.seconds + delay.microseconds/1e6)

    def _vwap(self):
        if len(self._pxs):
            wp = sum([x*y for x,y in zip(self._pxs, self._volumes)])
            v = sum(self._volumes)

            return wp/v

    def on_message(self, msg):
        if 'last_size' in msg and 'price' in msg:
            last_size = float(msg['last_size'])
            price = float(msg['price'])
            self._bar_volume += last_size
            self._weighted_price += last_size * price
            self._ticks += 1
            if self._bar_high is None or price > self._bar_high:
                self._bar_high = price
            if self._bar_low is None or price < self._bar_low:
                self._bar_low = price
            self._pxs.append(price)
            self._volumes.append(last_size)
            logging.debug("VWAP: %s", self._vwap())
        self.last_msg = msg
        logging.debug("Message: %s", msg)

    def on_bar(self, loop):
        if self.last_msg is not None:
            if self._bar_volume == 0:
                self.last_msg['vwap'] = None
            else:
                self.last_msg['vwap'] = self._weighted_price/self._bar_volume
            self.last_msg['bar_bar_volume'] = self._bar_volume
            self.last_msg['bar_ticks'] = self._ticks
            self.last_msg['bar_high'] = self._bar_high
            self.last_msg['bar_low'] = self._bar_low
            last = self.last_msg.get('price')
            if last:
                last = float(last)
            self._bar_high = last
            self._bar_low = last
            logging.info("Bar: %s", self.last_msg)
        self._bar_volume = 0
        self._weighted_price = 0.0
        self._ticks = 0
        self._pxs.clear()
        self._volumes.clear()
        // reschedule
        loop.call_at(loop.time() + self.next_minute_delay(),
                     functools.partial(self.on_bar, loop))

def main():
    argparser = argparse.ArgumentParser()
    argparser.add_argument("--product", default="BTC-USD",
                           help="coinbase product")
    argparser.add_argument('-d', "--debug", action='store_true',
                           help="debug logging")
    args = argparser.parse_args()

    cfg = {"format": "%(asctime)s - %(levelname)s - %(message)s"}
    if args.debug:
        cfg["level"] = logging.DEBUG
    else:
        cfg["level"] = logging.INFO

    logging.basicConfig(**cfg)

    client = BarClient(url="wss://ws-feed.pro.coinbase.com",
                       products=args.product,
                       channels=["ticker"])

    loop = asyncio.get_event_loop()
    loop.call_at(loop.time() + client.next_minute_delay(), functools.partial(client.on_bar, loop))
    loop.call_soon(client.start)

    try:
        loop.run_forever()
    finally:
        loop.close()

if __name__ == ' __main__':
    main()

Запуск примера

Чтобы запустить этот код, вам нужно установить несколько дополнительных модулей. Модуль CBPRO – это простая обертка Python API Coinbase. Стрелка Хорошая библиотека для выполнения логики DateTime.

pip install arrow cbpro

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

 ./coinbase_client.py -d
2021-03-14 17:20:12,828 - DEBUG - Using selector: KqueueSelector
-- Subscribed! --

2021-03-14 17:20:13,059 - DEBUG - Message: {'type': 'subscriptions', 'channels': [{'name': 'ticker', 'product_ids': ['BTC-USD']}]}
2021-03-14 17:20:13,060 - DEBUG - VWAP: 60132.57

Профилирование примера

Теперь давайте рассмотрим команды Py-Spy. Во -первых, использование команды дампа даст нам быстрый вид стека, переведенный на функции Python.

Быстрое примечание здесь, если вы используете Mac, вам нужно будет запустить Py-Spy в качестве Sudo. На Linux это зависит от ваших настроек безопасности. Кроме того, так как я использовал Pyenv, мне нужно было передать свою среду в Sudo, используя -E Флаг, чтобы он поднимает правый интерпретатор Python и сценарий Py-Spy на пути. Я получил идентификатор процесса для моего процесса, используя ps командование в моей оболочке (в моем случае это было 97520).

Py-Spy Damp

 sudo -E py-spy dump -p 97520
Process 97520: /Users/mcw/.pyenv/versions/py-spy/bin/python ./coinbase_client.py -d
Python v3.8.7 (/Users/mcw/.pyenv/versions/3.8.7/bin/python3.8)

Thread 0x113206DC0 (idle): "MainThread"
    select (selectors.py:558)
    _run_once (asyncio/base_events.py:1823)
    run_forever (asyncio/base_events.py:570)
    main (coinbase_client.py:107)
     (coinbase_client.py:113)
Thread 0x700009CAA000 (idle): "Thread-1"
    read (ssl.py:1101)
    recv (ssl.py:1226)
    recv (websocket/_socket.py:80)
    _recv (websocket/_core.py:427)
    recv_strict (websocket/_abnf.py:371)
    recv_header (websocket/_abnf.py:286)
    recv_frame (websocket/_abnf.py:336)
    recv_frame (websocket/_core.py:357)
    recv_data_frame (websocket/_core.py:323)
    recv_data (websocket/_core.py:310)
    recv (websocket/_core.py:293)
    _listen (cbpro/websocket_client.py:84)
    _go (cbpro/websocket_client.py:41)
    run (threading.py:870)
    _bootstrap_inner (threading.py:932)
    _bootstrap (threading.py:890)

Вы можете видеть, что работает две темы. Один из них читает данные, другой – в Выберите В петле бега. Это полезно только для профилирования, если наша программа застряла. Одна действительно хорошая особенность, хотя, если вы дадите ей -Локал Вариант, он покажет вам любые локальные переменные, которые могут быть очень полезны для отладки!

Py-Spy Top

Следующая команда, чтобы попробовать, это Верх Анкет

sudo -E py-spy top -p 97520

Это вызовет интерфейс, который выглядит очень похоже на Unix Верх командование Когда ваша программа работает, и Py-Spy собирает образцы, она покажет вам, где она проводит время. Вот скриншот того, как это выглядело для меня примерно через 30 секунд.

py-spy Верхний вывод

Py-Spy Record

Наконец, вы можете записать данные, используя Py-Spy для последующего анализа или вывода. Существует необработанный формат, формат SpeedScope и вывод фламеграфа. Вы можете указать количество времени, которое вы хотите собирать данные (в секунды), или просто позволить ему собирать данные, пока вы не выйдете из программы, используя CTRL-C. Например, эта команда будет генерировать полезный маленький фламеграф SVG, с которым вы можете взаимодействовать в веб -браузере.

sudo -E py-spy record -p 97520 --output py-spy.svg

Вы также можете экспортировать данные в SpeedScope Формат, а затем загрузите его в веб -инструмент SpeedScope для дальнейшего анализа. Это отличный инструмент для интерактивного вида, как выполняется ваш код.

Я бы посоветовал вам запустить этот код самостоятельно и играть как с инструментом SpeedScope, так и с выходом SVG, но вот два снимка экрана, которые помогают объяснить, как он работает. Это первое представление – общий выход SVG. Если вы будете зависать над ячейками, это покажет вам детали функции. Вы можете видеть, что большую часть времени проводится в клиенте WebSocket _Listen метод Но on_message Метод отображается справа от этого (обозначается стрелкой)

py-spy svg вывод

Если мы нажмем на него, мы получим подробный вид.

Py-Spy Svg Подробный выход

В моем случае я вижу, что мой список понимания и регистрация в нединешнем _VWAP Метод появляется довольно высоко в профиле. Я могу легко удалить этот метод (и дополнительные цены и объемы, которые я отслеживал), так как VWAP можно рассчитать только с помощью работающего продукта и общего объема (как я уже делаю в коде). Также интересно посмотреть, когда сценарий запускается в режиме отладки, сколько времени в журнале регистрации принимает

Резюме

Таким образом, я бы посоветовал вам попробовать Py-Spy на некоторых из вашего кода. Если вы попытаетесь предсказать, где ваш код будет тратить свое время, насколько вы верны? Есть ли вы выводы, которые вас удивляют? Возможно, сравните вывод PY-Spy с детерминированным профилировщиком, таким как LINE_PROFILER.

Я надеюсь, что этот обзор Py-Spy был полезен, и вы можете развернуть этот инструмент в диагностике проблем с производительностью в своем собственном коде.

Пост Профилирование кода Python с py-spy появился первым на wrighters.io Анкет

Оригинал: “https://dev.to/wrighter/profiling-python-code-with-py-spy-17oj”