Python
April 4

Профилирование Python-кода: как найти, где тормозит ваш код

Python — отличный язык, пока всё работает быстро. Но однажды ваш код начинает тормозить, и вы сидите с кофе, глядя на for-цикл, который работает "вечно".
И тут приходит время профилирования.

Профилирование — это как наблюдение за котом: вы хотите понять, куда он тратит время. Ест? Спит? Пялится в стену?

❓ Что такое профилирование?

Профилирование (profiling) — это процесс измерения производительности кода:

  • Сколько времени тратится на выполнение?
  • Какие функции вызываются чаще всего?
  • Где программа "застревает"?
  • Сколько памяти расходуется?

Профилирование отвечает на главный вопрос: "Где именно нужно оптимизировать?"

🐱 Пример: вы можете думать, что ваша программа тормозит из-за чтения файла… а на деле она 90% времени сортирует уже готовые данные.

📦 Виды профилирования

Профилирование бывает нескольких видов:

1. По времени выполнения

Отвечает на вопрос: что работает дольше всего?
В этом помогают cProfile, line_profiler, timeit.

2. По потреблению памяти

Где создаются большие объекты? Есть ли утечки?
Инструменты: memory_profiler, objgraph, tracemalloc.

3. По частоте вызовов

Какие функции вызываются слишком часто? Иногда частый вызов простой функции может сильно замедлять программу.

4. По уровню строки

Какие конкретные строки кода тормозят?
Ответ даёт line_profiler.

5. Сэмплирующее профилирование

Инструмент "смотрит" в случайный момент времени и делает срез. Работает быстрее, но не даёт 100% точности. Используется в визуальных профайлерах типа SnakeViz, Py-Spy.

🧰 Основные инструменты профилирования в Python

1. cProfile — встроенный профилировщик

Это стандартный инструмент, встроенный в Python. Используется так:

python -m cProfile my_script.py

Или прямо в коде:

import cProfile

def main():
    # ваш код
    pass

cProfile.run('main()')

Результат: список функций, их вызовы и общее время выполнения, например:

         5 function calls in 0.000 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 test.py:1(<module>)
        1    0.000    0.000    0.000    0.000 test.py:1(main)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.print}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

📊 Как читать результаты?

Вот на что стоит обращать внимание:

ncalls Кол-во вызовов функции

tottime Время, потраченное только в этой функции cumtime Время, включая вызовы вложенных функций

percall Среднее время за вызов

Ищите функции с большим cumtime — они тормозят больше всего.

2. pstats и сортировка результатов

Если хочется увидеть, где конкретно тормозит:

import cProfile
import pstats

profiler = cProfile.Profile()
profiler.enable()

# код
main()

profiler.disable()
stats = pstats.Stats(profiler).sort_stats('cumtime')
stats.print_stats(10)  # топ-10 самых затратных функций
📌 cumtime — это суммарное время на функцию и всё, что она вызывает.

3. line_profiler — построчный анализ

Устанавливается отдельно:

pip install line_profiler

Используется с декоратором @profile:

@profile
def slow_function():
    result = 0
    for i in range(10000):
        result += i ** 2
    return result

Запуск:

kernprof -l my_script.py
python -m line_profiler my_script.py.lprof
🐱 Это как если бы вы поставили камеру над каждой строчкой кода и следили, где кот задерживается дольше всего.

4. memory_profiler — куда уходит память

Похож на line_profiler, но для RAM:

pip install memory_profiler

Код:

from memory_profiler import profile

@profile
def heavy_func():
    a = [x for x in range(1000000)]
    return a

Запуск:

python -m memory_profiler my_script.py

5. timeit — для измерения мелочей

Если вы хотите просто узнать, какая конструкция быстрее:

import timeit

print(timeit.timeit("sum(range(100))", number=10000))

Полезно для микробенчмарков — быстро сравнить map() против for.



🔍 Советы по профилированию

  • Не оптимизируй вслепую. Сначала замерь, потом думай.
  • Фокус на "узкие места" — 10% кода может тратить 90% времени.
  • Сравни до и после. Сделал оптимизацию? Проверь, стала ли она лучше.
  • Профилируй настоящие сценарии. Тестовые данные часто обманывают.
  • Учитывай сторонние библиотеки. Иногда pandas тормозит больше, чем ваш код.

🐾 Кошачий факт

Профилирование — это как установка камеры на кухне: вы думаете, что корм уходит за минуту, а оказывается — кот сидит и смотрит на миску 40 секунд, а потом ест за 3. Аналогично и с функциями: одна строчка может тормозить всё приложение.

✅ Вывод

Профилирование — это не про оптимизацию ради цифр, а про понимание, куда действительно уходит время и ресурсы.

Не гадайте — замеряйте. А уже потом думайте, где заменить кота… или append() на генератор.