Профилирование multiprocessing, Celery, subprocess, флаг --children
Когда ваш код запускает другие процессы, они не исчезают из профиля. Нужно просто попросить py-spy посмотреть на них.
Многие Python-приложения используют несколько процессов:
По умолчанию py-spy профилирует только один процесс. Но есть флаг --children.
# Профилировать только главный процесс
py-spy record -o profile.svg --pid 12345
# Профилировать главный процесс + все дочерние
py-spy record -o profile.svg --children --pid 12345py-spy отслеживает иерархию процессов:
main.py (PID 12345)
├── worker_1.py (PID 12346)
├── worker_2.py (PID 12347)
└── worker_3.py (PID 12348)
С флагом --children py-spy будет профилировать все процессы в дереве.
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.pypy-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 worker
celery -A myapp worker --loglevel=info
# Найдите PID главного процесса воркера
ps aux | grep celery
# Профилируйте все процессы воркера
py-spy record -o celery_profile.svg --children --pid <MAIN_PID>Вы увидите:
┌─────────────────────────────────────────────────────┐
│ celery.worker.loop (10%) │
├─────────────────────────────────────────────────────┤
│ process_and_save (75%) │
│ send_notifications (10%) │
│ другие задачи (5%) │
└─────────────────────────────────────────────────────┘
Если ваш код запускает внешние команды через subprocess:
import subprocess
def process_images():
"""Запуск ImageMagick для обработки изображений"""
subprocess.run([
'convert', 'input.jpg', '-resize', '50%', 'output.jpg'
])Важно: --children профилирует только Python-процессы. Внешние команды (ImageMagick, ffmpeg) не будут показаны.
Для профилирования внешних команд используйте --native (но это работает только для C-кода, вызываемого из Python, не для отдельных процессов).
Аналогично 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Только Python: Профилируются только Python-процессы. Внешние команды (bash, ffmpeg) не показываются.
Оверфайл: Каждый дочерний процесс добавляет оверхед. Для 10+ процессов рассмотрите профилирование только главного.
SVG может стать большим: Много процессов = большой SVG. Используйте --format speedscope для компактности.
Если процессов много, можно профилировать только один:
# Найдите PID конкретного воркера
ps aux | grep worker
# Профилируйте только его
py-spy record -o worker_1.svg --pid 123464 воркера multiprocessing, но задача выполняется 10 минут вместо ожидаемых 2-3.
py-spy record -o mp_profile.svg --children -- python slow_mp.pyОткройте 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. Это узкое место.
Возможно, process_item можно ускорить или использовать более эффективные структуры данных.
Celery воркер обрабатывает 1 задачу в секунду вместо 10.
ps aux | grep celery
# celery: main process PID 12345
# celery: worker process PID 12346
# celery: worker process PID 12347py-spy record -o celery.svg --children --pid 12345Вы видите:
┌─────────────────────────────────────────────────────┐
│ celery.worker.loop (60%) │ ← Много времени на координацию!
├─────────────────────────────────────────────────────┤
│ actual_task (30%) │
│ другие (10%) │
└─────────────────────────────────────────────────────┘
Диагноз: 60% времени тратится на celery.worker.loop — координацию, а не на задачи.
Возможные причины:
Решение:
worker_prefetch_multipliertask_acks_late| Без --children | С --children |
|---|---|
| Только главный процесс | Все дочерние процессы |
| Быстрее, меньше файл | Медленнее, больше файл |
| Не видно дисбаланс | Видно нагрузку на каждый процесс |
Рекомендация: Используйте --children по умолчанию для multiprocessing/Celery. Отключайте только если процессов очень много (20+).
py-spy record -o profile.svg -- python mp_script.pyКлючевая идея: Флаг --children позволяет увидеть полную картину производительности многопроцессных приложений. Без него вы видите только верхушку айсберга.
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.