← Назад к вопросам

Как отличить медленный код от быстрого?

1.0 Junior🔥 71 комментариев
#Soft Skills

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Профилирование кода: выявление узких мест

Лучший способ отличить медленный код — это не гадать, а измерить. Python предоставляет отличные инструменты профилирования.

1. Базовое измерение времени

Простой способ с time.time():

import time

def slow_function():
    time.sleep(1)
    return "result"

start = time.time()
result = slow_function()
end = time.time()

print(f"Время выполнения: {end - start:.4f} сек")
# Выход: Время выполнения: 1.0001 сек

Более удобно с декоратором:

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__}: {end - start:.4f} сек")
        return result
    return wrapper

@timer
def process_data(n):
    return sum(range(n))

process_data(10000000)
# Выход: process_data: 0.5123 сек

2. Модуль timeit для микробенчмарков

Сравнение разных реализаций:

import timeit

# Подход 1: List comprehension
setup1 = "squares = [x**2 for x in range(1000)]"

# Подход 2: Цикл
setup2 = """
squares = []
for x in range(1000):
    squares.append(x**2)
"""

time1 = timeit.timeit(setup1, number=10000)
time2 = timeit.timeit(setup2, number=10000)

print(f"List comprehension: {time1:.4f} сек")
print(f"Цикл с append: {time2:.4f} сек")
print(f"Ускорение: {time2/time1:.2f}x")

Из командной строки:

# Сравнить скорость разных подходов
python -m timeit "sum(range(1000))"
# 100000 loops, best of 5: 23.7 usec per loop

python -m timeit "result = 0; [result := result + i for i in range(1000)]"
# 10000 loops, best of 5: 45.2 usec per loop

3. cProfile: полный анализ функций

Найти самые медленные функции:

import cProfile
import pstats

def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

def process():
    result = 0
    for i in range(10):
        result += fibonacci(30)
    return result

# Профилирование
profiler = cProfile.Profile()
profiler.enable()
process()
profiler.disable()

# Вывод результатов
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')  # Отсортировать по общему времени
stats.print_stats(10)  # Топ-10 функций

Вывод:

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
177160001    0.567    0.000    0.567    0.000 script.py:1(fibonacci)
        1    0.000    0.000    0.873    0.873 script.py:7(process)
        1    0.000    0.000    0.873    0.873 {built-in method exec}

Из командной строки:

python -m cProfile -s cumulative script.py | head -20

4. line_profiler для анализа по строкам

# pip install line-profiler

from line_profiler import LineProfiler

def slow_function():
    result = 0
    for i in range(1000000):
        result += i ** 2  # Эта строка медленная
    return result

lpr = LineProfiler()
lpr.add_function(slow_function)
lpr.enable()
slow_function()
lpr.disable()
lpr.print_stats()

Вывод:

Line #  Hits      Time  Per Hit  % Time  Line Contents
    1                                    def slow_function():
    2      1      1.0    1.0    0.0      result = 0
    3 1000001 524123.0    0.5   99.9      for i in range(1000000):
    4 1000000 522123.0    0.5   99.8          result += i ** 2
    5      1      2.0    2.0    0.0      return result

5. memory_profiler для анализа памяти

# pip install memory-profiler

from memory_profiler import profile

@profile
def create_list():
    big_list = [i ** 2 for i in range(1000000)]
    return sum(big_list)

if __name__ == '__main__':
    create_list()

Запуск:

python -m memory_profiler script.py

Вывод:

Line #    Mem usage    Increment  Line Contents
     2   40.3 MiB      0.0 MiB    @profile
     3   40.3 MiB      0.0 MiB    def create_list():
     4   70.5 MiB     30.2 MiB        big_list = [i**2 for i in range(1000000)]
     5   70.5 MiB      0.0 MiB        return sum(big_list)

6. Практические примеры: медленный vs быстрый код

Пример 1: Поиск в списке

import timeit

# МЕДЛЕННО: линейный поиск в списке каждый раз
data = list(range(10000))
code_slow = """
for item in [5000, 7500, 2500, 9999, 1000]:
    if item in data:
        pass
"""

# БЫСТРО: преобразуем в set
data_set = set(range(10000))
code_fast = """
for item in [5000, 7500, 2500, 9999, 1000]:
    if item in data_set:
        pass
"""

time_slow = timeit.timeit(code_slow, globals=globals(), number=10000)
time_fast = timeit.timeit(code_fast, globals=globals(), number=10000)

print(f"Медленно: {time_slow:.4f} сек")
print(f"Быстро: {time_fast:.4f} сек")
print(f"Ускорение: {time_slow / time_fast:.1f}x")

Пример 2: Конкатенация строк

# МЕДЛЕННО: +=
code_slow = """
result = ""
for i in range(1000):
    result += str(i)
"""

# БЫСТРО: join
code_fast = """
result = "".join(str(i) for i in range(1000))
"""

time_slow = timeit.timeit(code_slow, number=1000)
time_fast = timeit.timeit(code_fast, number=1000)

print(f"Медленно (+=): {time_slow:.4f} сек")
print(f"Быстро (join): {time_fast:.4f} сек")
print(f"Ускорение: {time_slow / time_fast:.1f}x")

Пример 3: Работа с БД

# МЕДЛЕННО: N+1 запросов
users = db.users.find_all()  # 1 запрос
for user in users:
    posts = db.posts.find_by_user(user.id)  # N запросов!
    process(user, posts)

# БЫСТРО: JOIN или eager loading
users_with_posts = db.query("""
    SELECT users.*, posts.*
    FROM users
    LEFT JOIN posts ON users.id = posts.user_id
""")
for user, posts in group_by_user(users_with_posts):
    process(user, posts)

7. Встроенная диагностика в FastAPI

from fastapi import FastAPI
import time
import logging

app = FastAPI()
logger = logging.getLogger(__name__)

@app.middleware("http")
async def log_request_time(request, call_next):
    start = time.perf_counter()
    response = await call_next(request)
    process_time = time.perf_counter() - start
    
    logger.info(f"{request.method} {request.url.path}: {process_time:.3f} сек")
    response.headers["X-Process-Time"] = str(process_time)
    
    return response

8. Чеклист для выявления проблем

Спросите себя:

  1. Сложность алгоритма:

    • Использую ли я O(n²) где можно O(n)?
    • Есть ли вложенные циклы?
    • Использую ли я неправильную структуру данных?
  2. I/O операции:

    • Выполняю ли я запросы в цикле (N+1)?
    • Использую ли я синхронные вызовы где нужны асинхронные?
    • Нет ли лишних сетевых запросов?
  3. Память:

    • Создаю ли я большие объекты в памяти?
    • Есть ли утечки памяти?
    • Правильно ли освобождаю ресурсы?
  4. Конкурентность:

    • Есть ли блокирующие операции?
    • Использую ли я asyncio/threading где нужно?

Итог

Инструменты:

  • timeit — быстрые микробенчмарки
  • cProfile — найти самые медленные функции
  • line_profiler — анализ по строкам кода
  • memory_profiler — поиск утечек памяти
  • middleware в FastAPI — профилирование запросов

Золотое правило: "Измеряй, не гадай!" Всегда профилируй перед оптимизацией.