Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Дочерние процессы
subprocess

Дочерние процессы

Профилирование multiprocessing, Celery, subprocess, флаг --children

Профилирование дочерних процессов

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

Многие Python-приложения используют несколько процессов:

  • multiprocessing для обхода GIL
  • Celery для фоновых задач
  • subprocess для запуска внешних команд
  • concurrent.futures.ProcessPoolExecutor

По умолчанию py-spy профилирует только один процесс. Но есть флаг --children.

#Базовое использование

# Профилировать только главный процесс py-spy record -o profile.svg --pid 12345 # Профилировать главный процесс + все дочерние py-spy record -o profile.svg --children --pid 12345

#Как это работает

py-spy отслеживает иерархию процессов:

main.py (PID 12345)
├── worker_1.py (PID 12346)
├── worker_2.py (PID 12347)
└── worker_3.py (PID 12348)

С флагом --children py-spy будет профилировать все процессы в дереве.

#Профилирование multiprocessing

#Пример кода

from multiprocessing import Process, Queue import time import math def worker(n, queue): """Тяжёлые вычисления в отдельном процессе""" result = 0 for i in range(10_000_000): result += math.sqrt(i) queue.put(result) print(f"Worker {n} finished") def main(): queue = Queue() processes = [] # Запускаем 4 процесса for i in range(4): p = Process(target=worker, args=(i, queue)) p.start() processes.append(p) # Ждём завершения for p in processes: p.join() # Получаем результаты results = [queue.get() for _ in range(4)] print(f"Total: {sum(results)}") if __name__ == "__main__": main()

#Запуск профилирования

Способ 1: Профилирование с запуском

py-spy record -o profile.svg -- python mp_script.py

py-spy автоматически обнаружит дочерние процессы.

Способ 2: Профилирование работающего процесса

# Запустите скрипт python mp_script.py # Найдите PID главного процесса ps aux | grep mp_script # Профилируйте с дочерними py-spy record -o profile.svg --children --pid 12345

#Анализ результата

Откройте profile.svg. Вы увидите:

┌─────────────────────────────────────────────────────┐
│                    main (15%)                       │
├─────────────────────────────────────────────────────┤
│              worker (85% - суммарно)                │
│  ┌─────────────┬─────────────┬─────────────┬──────┐│
│  │worker_1(21%)│worker_2(22%)│worker_3(21%)│...   ││
│  └─────────────┴─────────────┴─────────────┴──────┘│
└─────────────────────────────────────────────────────┘

Важно: Каждый процесс показывается отдельно. Вы можете увидеть, что:

  • Один процесс работает медленнее других (дисбаланс нагрузки)
  • Все процессы ждут один и тот же ресурс (блокировка)
  • Главный процесс тратит время на координацию, а не на работу

#Профилирование Celery

Celery использует воркеры — отдельные процессы для выполнения задач.

#Запуск профилирования

# Запустите Celery worker celery -A myapp worker --loglevel=info # Найдите PID главного процесса воркера ps aux | grep celery # Профилируйте все процессы воркера py-spy record -o celery_profile.svg --children --pid <MAIN_PID>

#Анализ

Вы увидите:

  • Какой процент времени воркеры проводят в задачах
  • Какие задачи самые медленные
  • Сколько времени тратится на накладные расходы Celery

#Пример вывода

┌─────────────────────────────────────────────────────┐
│              celery.worker.loop (10%)               │
├─────────────────────────────────────────────────────┤
│           process_and_save (75%)                    │
│           send_notifications (10%)                  │
│           другие задачи (5%)                        │
└─────────────────────────────────────────────────────┘

#Профилирование subprocess

Если ваш код запускает внешние команды через subprocess:

import subprocess def process_images(): """Запуск ImageMagick для обработки изображений""" subprocess.run([ 'convert', 'input.jpg', '-resize', '50%', 'output.jpg' ])

Важно: --children профилирует только Python-процессы. Внешние команды (ImageMagick, ffmpeg) не будут показаны.

Для профилирования внешних команд используйте --native (но это работает только для C-кода, вызываемого из Python, не для отдельных процессов).

#ProcessPoolExecutor

Аналогично multiprocessing:

from concurrent.futures import ProcessPoolExecutor import math def compute(n): return sum(math.sqrt(i) for i in range(10_000_000)) with ProcessPoolExecutor(max_workers=4) as executor: results = list(executor.map(compute, range(4)))

Профилирование:

py-spy record -o profile.svg -- python executor_script.py

#Ограничения --children

  1. Только Python: Профилируются только Python-процессы. Внешние команды (bash, ffmpeg) не показываются.

  2. Оверфайл: Каждый дочерний процесс добавляет оверхед. Для 10+ процессов рассмотрите профилирование только главного.

  3. SVG может стать большим: Много процессов = большой SVG. Используйте --format speedscope для компактности.

#Альтернатива: Профилирование конкретного процесса

Если процессов много, можно профилировать только один:

# Найдите PID конкретного воркера ps aux | grep worker # Профилируйте только его py-spy record -o worker_1.svg --pid 12346

#Практический пример: Поиск дисбаланса нагрузки

#Сценарий

4 воркера multiprocessing, но задача выполняется 10 минут вместо ожидаемых 2-3.

#Шаг 1: Запустите профилирование

py-spy record -o mp_profile.svg --children -- python slow_mp.py

#Шаг 2: Анализ

Откройте SVG. Вы видите:

worker_1: 25%
worker_2: 25%
worker_3: 25%
worker_4: 25%

Все процессы работают равномерно — это хорошо. Но общее время всё равно большое.

Смотрите глубже:

worker_1 → process_item: 90%
worker_2 → process_item: 90%
worker_3 → process_item: 90%
worker_4 → process_item: 90%

Все воркеры проводят 90% времени в process_item. Это узкое место.

#Шаг 3: Оптимизация

Возможно, process_item можно ускорить или использовать более эффективные структуры данных.

#Практический пример: Celery воркер тормозит

#Сценарий

Celery воркер обрабатывает 1 задачу в секунду вместо 10.

#Шаг 1: Найдите PID

ps aux | grep celery # celery: main process PID 12345 # celery: worker process PID 12346 # celery: worker process PID 12347

#Шаг 2: Профилирование

py-spy record -o celery.svg --children --pid 12345

#Шаг 3: Анализ

Вы видите:

┌─────────────────────────────────────────────────────┐
│              celery.worker.loop (60%)               │ ← Много времени на координацию!
├─────────────────────────────────────────────────────┤
│           actual_task (30%)                         │
│           другие (10%)                              │
└─────────────────────────────────────────────────────┘

Диагноз: 60% времени тратится на celery.worker.loop — координацию, а не на задачи.

Возможные причины:

  • Слишком частый поллинг очереди
  • Медленный брокер (Redis/RabbitMQ)
  • Много маленьких задач, накладные расходы велики

Решение:

  • Увеличьте worker_prefetch_multiplier
  • Используйте task_acks_late
  • Объедините маленькие задачи в большие

#Сравнение: с --children и без

Без --childrenС --children
Только главный процессВсе дочерние процессы
Быстрее, меньше файлМедленнее, больше файл
Не видно дисбалансВидно нагрузку на каждый процесс

Рекомендация: Используйте --children по умолчанию для multiprocessing/Celery. Отключайте только если процессов очень много (20+).

#Практическое задание

  1. Создайте скрипт с multiprocessing (пример выше)
  2. Запустите профилирование: py-spy record -o profile.svg -- python mp_script.py
  3. Откройте SVG
  4. Найдите, сколько времени занимает каждый воркер
  5. Попробуйте изменить количество воркеров и сравнить профили

Ключевая идея: Флаг --children позволяет увидеть полную картину производительности многопроцессных приложений. Без него вы видите только верхушку айсберга.

Далее: Нативные расширения