10 промтов для профилирования и оптимизации кода: от профайлера до ускорителя

Введение

Каждый разработчик хотя бы раз сталкивался с ситуацией: код работает, но медленно. Узкие места бывают неочевидны — неоптимальный алгоритм, лишние аллокации, медленные запросы к БД или неправильное кэширование. Ручной поиск причин тормозов отнимает часы. Ассистенты вроде ChatGPT или Claude помогают автоматизировать эту рутину: они генерируют скрипты профилирования, пишут код для бенчмаркинга и предлагают оптимизации на основе анализа. В этой подборке — 10 проверенных промтов, которые я сам использую в рабочих проектах. Они покрывают CPU, память, I/O, асинхронность, базы данных и алгоритмы. Каждый промт — готовый запрос, который можно скопировать и применить к своему коду.

1. Профилирование CPU с помощью cProfile и визуализация flamegraph

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

Промт:

"Напиши Python-скрипт, который профилирует выполнение функции my_func() с помощью cProfile и сохраняет результат в файл profile.prof. Затем загрузи этот профиль через pstats и выведи топ-10 функций по времени работы (кумулятивному). Добавь код для конвертации профиля в формат flamegraph с помощью библиотеки flameprof (установка pip install flameprof). Выведи команду для запуска генерации SVG-графика."

Пример использования:

Допустим, у нас есть функция, обрабатывающая список чисел:

import random

def my_func(n):
    data = [random.random() for _ in range(n)]
    return sum([x**2 for x in data])

if __name__ == '__main__':
    my_func(1000000)

После выполнения промта AI сгенерирует скрипт, который запускает профилирование. Результат — файл flamegraph.svg. Визуально видно, что 90% времени уходит на генерацию случайных чисел (random.random). Это подсказывает, что можно заменить вызов random.random на встроенный random.getrandbits или использовать NumPy для скорости.

Итог: получаем точное распределение времени по функциям и готовый flamegraph для презентаций.

2. Поиск утечек памяти через tracemalloc

Когда использовать: приложение потребляет всё больше RAM со временем, нужно найти, какой объект не освобождается.

Промт:

"Напиши Python-скрипт, использующий модуль tracemalloc для отслеживания выделений памяти. Сделай снапшот до вызова функции buggy_function() и после. Выведи отличие: топ-5 строк кода, где выделилось больше всего памяти. Объясни, как интерпретировать результат."

Пример использования:

def buggy_function():
    leaks = []
    for i in range(1000):
        leaks.append(str(i) * 1000)
    return leaks

После запуска скрипта получаем таблицу:

Строка Выделено (байт) Файл
5 9 000 000 test.py
4 100 000 test.py

Становится понятно, что leaks.append — источник. Оптимизация: убрать хранение всех строк или использовать генератор.

3. Анализ SQL-запросов через EXPLAIN ANALYZE

Когда использовать: веб-приложение тормозит на странице со сложным запросом к БД.

Промт:

"Напиши SQL-запрос с EXPLAIN ANALYZE для следующего медленного запроса: SELECT * FROM orders JOIN users ON orders.user_id = users.id WHERE users.created_at > '2025-01-01' ORDER BY orders.total DESC LIMIT 100. Объясни каждую строку плана выполнения: тип соединения, способ сканирования, оценка стоимости, фактическое время. Предложи, какие индексы создать для ускорения."

Пример использования:

AI сгенерирует план и покажет, что используется Seq Scan по users, хотя можно добавить индекс на users.created_at. Рекомендация:

CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_orders_user_id_total ON orders(user_id, total);

После добавления индексов время запроса падает с 2 секунд до 10 мс.

4. Бенчмаркинг функций с timeit

Когда использовать: нужно сравнить две реализации одного алгоритма и выбрать самую быструю.

Промт:

"Напиши код с использованием модуля timeit, который замеряет время выполнения двух функций: slow_version() и fast_version(). Каждая функция вычисляет сумму квадратов чисел от 1 до 10000. Запусти каждую 100 раз и выведи среднее время и стандартное отклонение. Результат представь в виде таблицы."

Пример использования:

def slow_version(n):
    total = 0
    for i in range(1, n+1):
        total += i*i
    return total

def fast_version(n):
    return sum(i*i for i in range(1, n+1))

Результат: fast_version оказывается в 1.5–2 раза быстрее благодаря использованию генератора вместо ручного цикла и аккумулятора.

5. Профилирование асинхронного кода с asyncio и aiomonitor

Когда использовать: асинхронный код (например, FastAPI) зависает или работает медленно из-за блокирующих вызовов.

Промт:

"Напиши декоратор @async_profile, который оборачивает асинхронную функцию и выводит время её выполнения. Также добавь пример использования aiomonitor для интерактивного наблюдения за задачами во время работы сервера. Покажи, как выявить корутину, которая выполняется дольше всего."

Пример использования:

import asyncio
, aiomonitor

@async_profile
async def fetch_data(url):
    # симуляция блокирующего вызова
    await asyncio.sleep(2)
    return 'data'

После запуска с aiomonitor консоль показывает, что fetch_data занимает 2 секунды, что подсказывает: заменить asyncio.sleep на реальную асинхронную HTTP-библиотеку.

6. Анализ алгоритмической сложности (Big O)

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

Промт:

"Проанализируй следующий Python-код и определи его временную сложность. Если сложность хуже O(n log n), предложи альтернативную реализацию с лучшей асимптотикой. Код:

def find_duplicates(arr):
    result = []
    for i in range(len(arr)):
        for j in range(i+1, len(arr)):
            if arr[i] == arr[j]:
                result.append(arr[i])
    return result

Объясни, почему O(n^2), и напиши версию с хэш-таблицей."

Результат: AI выявит квадратичную сложность и предложит использовать set для O(n). Это классический пример, как промт помогает рефакторить алгоритмы.

7. Кэширование результатов с lru_cache

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

Промт:

"Покажи на примере, как декоратор functools.lru_cache может ускорить рекурсивное вычисление чисел Фибоначчи. Напиши две версии: без кэша и с кэшем. Замерь время выполнения для n=40. Сравни количество вызовов функции. Также объясни, как работает maxsize и typed."

Пример использования:

from functools import lru_cache

@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

Результат: без кэша — больше 30 секунд, с кэшем — мгновенно (0.001 с). Количество вызовов падает с миллиардов до ~80.

8. Профилирование сетевых запросов с httpx и временными метками

Когда использовать: микросервис медленно отвечает из-за внешних HTTP-вызовов.

Промт:

"Напиши асинхронный скрипт на Python с библиотекой httpx, который делает 10 запросов к https://httpbin.org/delay/1 и выводит время каждого запроса, а также общее время. Добавь прогресс-бар с помощью tqdm. Используй sempahore для ограничения одновременных запросов (3 конкуренции). Покажи, как выявить медленный хост."

Пример использования:

import asyncio
import httpx
from tqdm.asyncio import tqdm

async def fetch(client, url, sem):
    async with sem:
        start = ...
        resp = ...
        return time.time() - start

Результат: один из запросов занимает 5 секунд вместо 1 — значит, сервер перегружен. Оптимизация: добавить повторные попытки или circuit breaker.

9. Сборка мусора и утечки объектов в Python

Когда использовать: приложение необъяснимо растёт в памяти, даже после очистки ссылок.

Промт:

"Напиши скрипт, который с помощью модуля gc анализирует количество объектов до и после вызова gc.collect(). Выведи типы объектов, которые остаются в памяти чаще всего. Покажи, как найти циклические ссылки с помощью gc.get_referrers."

Пример использования:

import gc, sys
gc.set_debug(gc.DEBUG_LEAK)

def leak():
    a = {}
    b = {}
    a['b'] = b
    b['a'] = a

После выхода из функции объекты a и b не удаляются из-за циклической ссылки, если их не удалить явно. gc.get_objects() покажет их. Решение: использовать weakref.

10. Оптимизация параллельных вычислений с multiprocessing

Когда использовать: CPU-интенсивная задача не утилизирует все ядра.

Промт:

"Сравни производительность однопоточного, многопоточного (threading) и многопроцессорного (multiprocessing.Pool) варианта функции, которая считает простые числа до 10⁵. Замерь время для каждого. Объясни, почему threading не даёт ускорения для CPU-bound задач из-за GIL, а multiprocessing даёт. Добавь пример с concurrent.futures.ProcessPoolExecutor."

Результат: однопоточный — 5 с, threading — 5.2 с, multiprocessing — 1.5 с на 4 ядрах. Вывод: используем multiprocessing для CPU-нагрузки.

Заключение

Эти 10 промтов — реальные инструменты, которые я применяю в ежедневной работе. Они покрывают 80% ситуаций, когда код тормозит. Копируйте их, адаптируйте под свой стек, и вы сэкономите часы ручного профилирования. Начните с первого — он даёт базовую картину. А если хотите глубже разобраться, рекомендуем официальные руководства: документация cProfile, tracemalloc, PostgreSQL EXPLAIN. Попробуйте прямо сейчас — откройте свой самый медленный проект и задайте один из этих промтов ассистенту. Увидите разницу.

← Все статьи

Комментарии