Команда record, запись в SVG, анализ flame graph, поиск узких мест
Одна картинка стоит тысячи слов. Один SVG стоит тысячи догадок.
Команда record — основной инструмент py-spy для записи профиля. Она запускает программу (или подключается к существующей) и записывает данные в файл для последующего анализа.
py-spy record [ОПЦИИ] -- [КОМАНДА PYTHON]
py-spy record [ОПЦИИ] --pid <PID>Важно: Флаг -- отделяет аргументы py-spy от аргументов команды Python.
py-spy поддерживает несколько форматов:
| Формат | Флаг | Описание |
|---|---|---|
| flamegraph (SVG) | --format flamegraph | Интерактивный SVG для браузера |
| icicle (SVG) | --format icicle | Перевёрнутый flamegraph (по умолчанию) |
| raw | --format raw | Сырые данные для парсинга |
| speedscope | --format speedscope | Для speedscope.app |
Рекомендуется для большинства случаев. Открывается в любом браузере, интерактивный, можно отправить коллеге.
py-spy record -o profile.svg -- python my_script.pyРазница между flamegraph и icicle:
icicle более интуитивен — «растёт» вверх как вызовы функций.
Формат для speedscope.app — продвинутого веб-инструмента для анализа профилей.
py-spy record -o profile.speedscope --format speedscope -- python my_script.pyПреимущества speedscope:
Сырые данные для программной обработки:
py-spy record -o profile.raw --format raw -- python my_script.pyФормат: каждая строка содержит стек вызовов и количество сэмплов.
По умолчанию py-spy записывает 30 секунд или до завершения процесса.
# Записать 60 секунд
py-spy record -o profile.svg --duration 60 --pid 12345# Запись остановится, когда скрипт завершится
py-spy record -o profile.svg -- python long_running_script.py# Записывает, пока не нажмёте Ctrl+C
py-spy record -o profile.svg --duration 0 --pid 12345По умолчанию py-spy делает 100 снимков в секунду.
# 200 снимков в секунду — более точные данные
py-spy record -o profile.svg --rate 200 --pid 12345# 20 снимков в секунду — минимальный оверхед
py-spy record -o profile.svg --rate 20 --pid 12345Компромисс:
Откройте profile.svg в браузере. Вы увидите:
┌─────────────────────────────────────────────────────┐
│ process_data (45%) │ ← Самая широкая
├─────────────────────────────────────────────────────┤
│ parse_json (30%) save_to_db (15%) │
├─────────────────────┬─────────┴──────────┬──────────┤
│ extract_fields │ validate_schema │ query │
│ (18%) │ (12%) │ (10%) │
└─────────────────────┴────────────────────┴──────────┘
Ширина = время: Чем шире полоса, тем больше времени программа провела в этой функции.
Вертикаль = иерархия: Функции выше вызывают функции ниже.
Ищите широкие полосы: Это кандидаты на оптимизацию.
Обращайте внимание на «плечи»: Если функция широкая, но её дети узкие — время тратится внутри самой функции (циклы, вычисления).
SVG от py-spy интерактивный:
Создайте api_simulator.py:
import time
import json
import hashlib
def fetch_data():
"""Симуляция запроса к БД"""
time.sleep(0.1)
return {"users": list(range(1000))}
def validate_schema(data):
"""Валидация данных"""
time.sleep(0.05)
return True
def extract_fields(data):
"""Извлечение полей — намеренно медленное"""
result = []
for item in data["users"]:
# Неоптимально: создаём строку на каждой итерации
key = f"user_{item}"
hashed = hashlib.md5(key.encode()).hexdigest()
result.append({"id": item, "hash": hashed})
return result
def save_to_db(records):
"""Симуляция сохранения"""
time.sleep(0.08)
return len(records)
def process_request():
"""Обработка одного запроса"""
data = fetch_data()
if not validate_schema(data):
return None
records = extract_fields(data)
save_to_db(records)
return records
def main():
"""Симуляция 10 запросов"""
for i in range(10):
process_request()
print("Готово!")
if __name__ == "__main__":
main()py-spy record -o api_profile.svg -- python api_simulator.pyОткройте api_profile.svg. Вы увидите:
Вывод: Оптимизируйте extract_fields. Например, уберите создание строки f"user_{item}" внутри цикла.
def extract_fields_optimized(data):
"""Оптимизированная версия"""
result = []
for item in data["users"]:
# Оптимизация: хэшируем число напрямую
key = str(item)
hashed = hashlib.md5(key.encode()).hexdigest()
result.append({"id": item, "hash": hashed})
return resultЗапрофилируйте ещё раз — extract_fields должна стать уже.
Если скрипт принимает аргументы:
py-spy record -o profile.svg -- python my_script.py --arg1 value1 --arg2 value2py-spy record -o profile.svg -- python -m my_modulepy-spy record -o profile.svg -- env FLASK_ENV=production python app.pyНа Linux/macOS может потребоваться sudo:
sudo py-spy record -o profile.svg --pid 12345Процесс завершился до запуска py-spy. Используйте -- для запуска с начала:
py-spy record -o profile.svg -- python my_script.pyПрограмма завершилась слишком быстро. Увеличьте длительность или количество данных:
py-spy record -o profile.svg --duration 60 --pid 12345Ключевая идея: record даёт вам постоянную запись профиля, которую можно анализировать, отправлять коллегам и сравнивать до/после оптимизации.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.