Python 3.15 отримав вбудований профайлер Tachyon: він підключається до працюючого процесу за ідентифікатором (PID) і показує, де програма витрачає час, без змін у коді й перезапуску. Станом на 4 жовтня 2026 року вийшов третій реліз-кандидат 3.15.0rc3, а фінальний випуск розробники перенесли на 9 жовтня через блокувальні помилки у відкладених імпортах.

Для програміста, який пише на Python щодня, це закриває давню прогалину: стандартна бібліотека вперше має інструмент, придатний для діагностики живого сервісу. Він живе в модулі profiling.sampling, що з'явився разом із новим пакетом profiling за PEP 799 (Python Enhancement Proposal, пропозиція щодо вдосконалення Python).

Вибіркове і трасувальне профілювання

Класичні cProfile і profile належать до трасувальних профайлерів: вони реєструють кожен виклик функції й повернення з неї. Кількість викликів виходить точною, але інтерпретатор виконує додаткову роботу на кожному кроці. Вибірковий профайлер діє інакше: з фіксованою частотою зчитує стек викликів процесу ззовні й рахує, які функції трапляються найчастіше.

Частота за замовчуванням становить 1 кГц, тобто тисяча знімків на секунду. Час у звіті є оцінкою: кількість семплів, помножена на інтервал. У прикладі з документації за 10 секунд на частоті 10 кГц набирається близько 100 000 семплів, і якщо функція трапилась у 5 000 із них, це приблизно 5% або 500 мілісекунд. Похибка при такій вибірці становить близько ±0,5%, а між запусками цифри трохи різнитимуться, тож дивіться на загальну картину.

Профілювання відповідає на запитання, де саме губиться час, і тільки після цього оптимізація має сенс.

Різницю між двома підходами зручно побачити в таблиці.

Критерій.profiling.sampling (Tachyon).profiling.tracing (cProfile).
Принцип.Періодичні знімки стека ззовні.Запис кожного виклику й повернення.
Накладні витрати.Майже нульові для цільової програми.Додаткові витрати на кожен виклик.
Кількість викликів.Оцінка за семплами.Точні значення.
Підключення до запущеного процесу.Так, за PID.Ні, профайлер вмикають під час старту або в коді.
Типове завдання.Пошук вузьких місць, діагностика живого сервісу.Точні лічильники й детальна статистика.

Модуль profile тепер застарілий і буде видалений у Python 3.17, а cProfile лишається псевдонімом нового profiling.tracing, тож старий код не зламається.

Що таке семпл і flame graph
Семпл — один знімок стека викликів у момент опитування процесу. Flame graph — діаграма, де кожна функція зображена прямокутником, а його ширина пропорційна кількості семплів, у яких функція траплялася.

Перший запуск у терміналі

Для експерименту візьмемо скрипт зі свідомо повільним місцем: видалення дублікатів через пошук у списку. Кожна перевірка r not in result проходить список повністю, тому витрати ростуть квадратично.

import random

def load_rows(n):
 return [random.randint(0, n // 2) for _ in range(n)]

def unique_slow(rows):
 result = []
 for r in rows:
 if r not in result: # лінійний пошук у списку
 result.append(r)
 return result

def summarize(rows):
 return sum(rows) / len(rows)

if __name__ == "__main__":
 rows = load_rows(40000)
 print(len(unique_slow(rows)), summarize(rows))

Запускаємо профайлер під інтерпретатором 3.15 (для перевірки підійде й 3.15.0rc3):

python -m profiling.sampling run report.py

За замовчуванням звіт виводиться таблицею з колонками nsamples, sample%, tottime, cumul% і cumtime. У nsamples числа записані як «прямі/сукупні»: прямі семпли набираються, коли функція була нагорі стека й виконувалась сама, сукупні — коли вона просто була в ланцюжку викликів.

У нашому скрипті очікувано, що unique_slow опиниться вгорі таблиці, а її прямі й сукупні семпли майже збігатимуться. Документація описує таку картину як гарячу точку, де процесор працює безпосередньо в тілі функції.

Для наочності додайте --flamegraph -o profile.html: сторінка відкривається в браузері без зовнішніх залежностей, ширина прямокутника пропорційна часу, а клік наближає потрібну гілку. Режим --heatmap накладає кількість семплів прямо на рядки вашого коду.

Робочий цикл оптимізації складається з чотирьох кроків:

  1. Запустіть скрипт під профайлером і знайдіть функцію з найбільшою кількістю прямих семплів.
  2. Збережіть базовий профіль у бінарному файлі, щоб було з чим порівнювати.
  3. Виправте код, наприклад замініть список на множину для перевірки наявності елемента.
  4. Запустіть скрипт ще раз і порівняйте результат із базовим профілем.

Останні два кроки зручно виконувати не на око, а через диференціальний flame graph.

Порівняння до і після виправлення

Базовий профіль записуємо у бінарний формат, а після зміни коду запускаємо профайлер із прапорцем порівняння.

python -m profiling.sampling run --binary -o baseline.bin report.py
# виправляємо unique_slow і запускаємо ще раз
python -m profiling.sampling run --diff-flamegraph baseline.bin -o diff.html report.py

На діаграмі червоним позначено функції, що почали споживати більше часу, синім — ті, що стали швидшими, сірим — без змін, фіолетовим — нові. Колір показує зміну прямого часу, тобто часу, коли функція була вгорі стека. Гілки, які зникли після оптимізації, можна переглянути окремо за перемикачем «elided».

Режими wall, cpu і gil

Один і той самий скрипт можна виміряти в різних режимах, і різниця між ними сама підказує причину повільності. Параметр --mode має чотири значення:

  • wall — усі семпли, зокрема очікування й сон;
  • cpu — лише виконання на ядрі процесора;
  • gil — лише моменти, коли потік тримає глобальне блокування інтерпретатора (GIL);
  • exception — лише потоки з активним винятком.

GIL дозволяє виконувати байткод лише одному потоку за раз. Wall вмикається за замовчуванням, і саме порівняння його з cpu найкраще відокремлює обчислення від очікування. Якщо функція високо і в одному профілі, і в другому, вона справді навантажує процесор, тож варто шукати кращий алгоритм.

Якщо в wall вона на вершині, а в cpu майже зникає, програма чекає на зовнішній ресурс. Тут допоможуть асинхронний ввід-вивід, пул з'єднань або скорочення очікувань. У прикладі з документації скрипт спить дві секунди й рахує суму квадратів: у wall переважає сон (близько 98% семплів), а в cpu сон відсутній і вгорі опиняється обчислення.

Режим gil потрібен для багатопотокових програм: він показує, які функції монополізують інтерпретатор і через що інші потоки не отримують часу. Нативний код розширень, як-от обчислення хешів, зазвичай звільняє GIL, тому в тому ж прикладі хешування займає близько 42% у режимі cpu і лише близько 5% у gil.

Підключення до живого процесу

Головна перевага Tachyon — команда attach: вона підключається до вже запущеного процесу за PID, збирає семпли заданий час і відключається. Застосунок не зупиняється й не відчуває спостереження. Розробники радять починати з короткого вікна в 10–30 секунд і знімати профіль під типовим навантаженням, а не під піком трафіку.

python -m profiling.sampling attach -d 30 --flamegraph -o profile.html 12345
python -m profiling.sampling dump 12345

Команда dump робить один знімок і виводить стек у вигляді трейсбеку з позначками стану кожного потоку, зокрема тримає він GIL чи працює на процесорі. Це зручно, коли сервіс завис і потрібно швидко побачити, на чому саме.

Операційна система не дозволяє читати пам'ять чужого процесу без дозволу, тому для attach потрібні права. У Linux це запуск від root, можливість CAP_SYS_PTRACE або зміна параметра ядра ptrace_scope, значення 1 за замовчуванням обмежує доступ батьківськими процесами. У macOS потрібні root або спеціальний дозвіл для налагоджувачів, у Windows — права адміністратора.

Є й обмеження за версіями: профайлер і цільовий процес мають працювати на одній мінорній версії Python. Підключитися з 3.14 до 3.15 неможливо, а для передрелізних збірок потрібен однаковий точний випуск. Для процесів на 3.12–3.14 лишається сторонній py-spy, який, за матеріалами подкасту Talk Python, вміє підключатися й до цих версій, хоч і з різною продуктивністю.

Для продакшну варто дочекатися фінального 3.15.0: сам реліз-кандидат розробники не рекомендують для промислових середовищ.

Що Tachyon не показує

Tachyon бачить лише кадри Python. Якщо більшу частину часу займає NumPy, pandas чи інше розширення на C, у звіті з'явиться функція, що його викликала, але не те, що відбувається всередині. Параметр --native додає позначки переходів у нативний код, проте детальний аналіз нативного коду потребує окремого інструмента.

Є й інші межі. Для скриптів, що виконуються менше секунди, семплів може не вистачити: використайте profiling.tracing або запустіть код у циклі. Точні лічильники викликів дає лише трасувальний профайлер, а різницю в 1–2% між двома варіантами краще міряти через timeit, бо шум вибірки її заховає.

Для асинхронного коду є режим --async-aware: він відновлює стеки за ланцюжками await, а не за внутрішніми кадрами циклу подій. Програми з multiprocessing підтримує параметр --subprocesses, який запускає окремий профайлер для кожного дочірнього процесу Python і створює окремий файл результатів.

Tachyon добре доповнює cProfile: вибірковий профайлер знаходить вузьке місце, трасувальний уточнює деталі. Разом із ним у 3.15 з'являться відкладені імпорти, вбудований frozendict і UTF-8 як кодування за замовчуванням. Напишіть у коментарях, на якому скрипті чи сервісі ви першим запустите Tachyon і що він покаже.

Рубрика «ПРОГРАМУВАННЯ»
2026-10-04 • Перегляди [ 10 ]

Оцінка - 5.0 (1)

 Схожі публікації