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

Как писать высокопроизводительный код?

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

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

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

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

Как писать высокопроизводительный код

Производительность — не магия, это система. Высокопроизводительный код пишется с самого начала, а не оптимизируется после. Рассмотрю систематический подход на примерах.

1. Профилирование первым делом

import cProfile
import pstats
from functools import wraps
import time

# Декоратор для быстрого профилирования
def profile_time(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f'{func.__name__}: {elapsed*1000:.2f}ms')
        return result
    return wrapper

# Полное профилирование
def profile_function(func):
    pr = cProfile.Profile()
    pr.enable()
    func()
    pr.disable()
    
    ps = pstats.Stats(pr)
    ps.sort_stats('cumulative')
    ps.print_stats(10)  # Топ 10 функций

# Memory профилирование
from memory_profiler import profile

@profile
def memory_intensive():
    big_list = [i for i in range(10_000_000)]
    return sum(big_list)

# Используй: python -m memory_profiler script.py

2. Алгоритмическая сложность первична

# ❌ O(n²) — медленно
def slow_duplicates(arr):
    for i in range(len(arr)):
        for j in range(i+1, len(arr)):
            if arr[i] == arr[j]:
                return True
    return False

# ✅ O(n) — быстро
def fast_duplicates(arr):
    seen = set()
    for item in arr:
        if item in seen:
            return True
        seen.add(item)
    return False

# Тест
import time
arr = list(range(100_000))

start = time.perf_counter()
slow_duplicates(arr)
print(f'Slow: {(time.perf_counter()-start)*1000:.1f}ms')  # ~5000ms

start = time.perf_counter()
fast_duplicates(arr)
print(f'Fast: {(time.perf_counter()-start)*1000:.1f}ms')  # ~10ms

# Разница в 500 раз!

3. Структуры данных имеют значение

import timeit

# Поиск в list — O(n)
data_list = list(range(1_000_000))
time_list = timeit.timeit(lambda: 999_999 in data_list, number=10)
print(f'List search: {time_list*100:.2f}ms')

# Поиск в set — O(1)
data_set = set(range(1_000_000))
time_set = timeit.timeit(lambda: 999_999 in data_set, number=10)
print(f'Set search: {time_set*100:.2f}ms')

# Разница в 1000x раз!

# Правильный выбор структур данных:
data_structures = {
    'list': {
        'access': 'O(1)',
        'insert': 'O(n)',
        'search': 'O(n)',
        'use_when': 'Нужны индексы и порядок'
    },
    'dict': {
        'access': 'O(1)',
        'insert': 'O(1)',
        'search': 'O(1)',
        'use_when': 'Ключ-значение поиск'
    },
    'set': {
        'access': 'no',
        'insert': 'O(1)',
        'search': 'O(1)',
        'use_when': 'Уникальные значения, поиск'
    },
    'heap': {
        'access': 'O(1) min',
        'insert': 'O(log n)',
        'search': 'O(n)',
        'use_when': 'Priority queue'
    }
}

4. Избегай ненужной работы

# ❌ Создаёт список из 1 млн элементов
big_range = list(range(1_000_000))
for i in big_range:
    print(i)

# ✅ Генератор — O(1) памяти
for i in range(1_000_000):
    print(i)

# ❌ Копирует список каждый раз
def slow():
    result = []
    for i in range(1000):
        new_list = result + [i]  # Копирует весь list!
        result = new_list
    return result

# ✅ Append модифицирует на месте
def fast():
    result = []
    for i in range(1000):
        result.append(i)  # O(1) в амортизированном
    return result

print(timeit.timeit(slow, number=100))  # ~200ms
print(timeit.timeit(fast, number=100))  # ~5ms

5. Кеширование и мемоизация

from functools import lru_cache
import time

# Медленная функция (без кеша)
def slow_fib(n):
    if n < 2:
        return n
    time.sleep(0.001)  # Имитация сложного вычисления
    return slow_fib(n-1) + slow_fib(n-2)

# С кешем
@lru_cache(maxsize=128)
def fast_fib(n):
    if n < 2:
        return n
    time.sleep(0.001)
    return fast_fib(n-1) + fast_fib(n-2)

# Для custom кеша
from functools import cache

@cache  # Python 3.9+, не требует maxsize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Кеш в production с Redis
import redis
from json import dumps, loads

class RedisCache:
    def __init__(self):
        self.redis = redis.Redis(host='localhost')
    
    def cached(self, ttl=3600):
        def decorator(func):
            def wrapper(*args, **kwargs):
                key = f'{func.__name__}:{args}:{kwargs}'
                
                # Пытаемся получить из кеша
                cached = self.redis.get(key)
                if cached:
                    return loads(cached)
                
                # Вычисляем и кешируем
                result = func(*args, **kwargs)
                self.redis.setex(key, ttl, dumps(result))
                return result
            return wrapper
        return decorator

cache = RedisCache()

@cache.cached(ttl=3600)
def expensive_operation(x, y):
    return x ** y

6. Батчинг и булк операции

import sqlite3

conn = sqlite3.connect(':memory:')
cursor = conn.cursor()

# ❌ Медленно: 10,000 отдельных INSERT
for i in range(10_000):
    cursor.execute('INSERT INTO users VALUES (?, ?)', (i, f'user{i}'))
conn.commit()  # ~5 сек

# ✅ Быстро: один bulk insert
data = [(i, f'user{i}') for i in range(10_000)]
cursor.executemany('INSERT INTO users VALUES (?, ?)', data)
conn.commit()  # ~50ms (в 100 раз быстрее!)

# В Django ORM
from django.db import models

# ❌ Медленно
for i in range(10_000):
    User.objects.create(name=f'user{i}')

# ✅ Быстро
users = [User(name=f'user{i}') for i in range(10_000)]
User.objects.bulk_create(users, batch_size=1000)

7. Ленивые вычисления и потоковая обработка

# ❌ Загружает всё в памяти
def process_large_file(filename):
    data = []
    with open(filename) as f:
        for line in f:
            data.append(line.strip())
    return [process(line) for line in data]  # Весь файл в памяти

# ✅ Потоковая обработка
def process_large_file(filename):
    with open(filename) as f:
        for line in f:
            yield process(line.strip())  # O(1) памяти

# Для параллельной обработки
from multiprocessing import Pool

def parallel_process(items):
    with Pool(4) as pool:  # 4 процесса
        results = pool.map(expensive_function, items)
    return results

# Для IO операций
import asyncio

async def fetch_many(urls):
    tasks = [fetch(url) for url in urls]
    return await asyncio.gather(*tasks)  # Параллельные запросы

8. Оптимизация циклови списков

# ❌ Медленно: функция вызывается каждый раз
result = []
for i in range(1_000_000):
    result.append(expensive_function(i))

# ✅ Быстро: list comprehension
result = [expensive_function(i) for i in range(1_000_000)]

# Ещё быстрее: без функции
result = [i * 2 for i in range(1_000_000)]

# Сравнение скорости
import timeit

f1 = timeit.timeit(lambda: [x*2 for x in range(10_000)], number=100)
f2 = timeit.timeit(lambda: [expensive_function(x) for x in range(10_000)], number=100)
f3 = timeit.timeit(lambda: list(map(expensive_function, range(10_000))), number=100)

print(f'List comp: {f1:.3f}s')
print(f'List comp с функцией: {f2:.3f}s')
print(f'Map: {f3:.3f}s')

# List comprehension обычно быстрее!

9. Использование NumPy для векторизации

import numpy as np

# ❌ Медленный Python цикл
def slow_sum(n):
    total = 0
    for i in range(n):
        total += i
    return total

# ✅ NumPy (в 100+ раз быстрее)
def fast_sum(n):
    return np.arange(n).sum()

n = 10_000_000
import timeit

print(timeit.timeit(lambda: slow_sum(n), number=1))  # ~500ms
print(timeit.timeit(lambda: fast_sum(n), number=1))  # ~5ms

# Когда использовать NumPy:
use_numpy = {
    'mathematical_operations': True,
    'large_arrays': True,
    'matrix_operations': True,
    'use_case': 'data processing, machine learning'
}

10. Чеклист высокопроизводительного кода

☐ ПРОФИЛИРУЙ! Сначала узнай где узкие места
☐ Алгоритмическая сложность O(n) лучше чем O(n²)
☐ Выбирай правильные структуры данных (set vs list)
☐ Избегай копирований (используй generator, slice views)
☐ Кешируй результаты (lru_cache, Redis)
☐ Батчинг: 10,000 x по 1 медленнее чем 1 x 10,000
☐ Ленивые вычисления (генераторы, потоковая обработка)
☐ List comprehension быстрее чем цикл
☐ NumPy для численных данных
☐ Multiprocessing для CPU-bound
☐ Asyncio для IO-bound
☐ Минимизируй исключения (дорогие)
☐ Избегай глобальных переменных
☐ Используй slots для больших объектов
☐ Компилируй критичный код (Cython, Numba)

11. Практичный пример: Оптимизация реального кода

# ДО: медленный код
def count_common_words(doc1, doc2):
    words1 = doc1.split()
    words2 = doc2.split()
    common = 0
    
    for word1 in words1:
        for word2 in words2:  # O(n²) !
            if word1 == word2:
                common += 1
    
    return common

# ПОСЛЕ: быстрый код
def count_common_words_fast(doc1, doc2):
    set1 = set(doc1.split())  # O(n)
    set2 = set(doc2.split())  # O(n)
    return len(set1 & set2)   # O(n) пересечение

# Улучшение на 1000x для больших документов!

12. Спецификация производительности (SLA)

def performance_spec(endpoint, qps, latency_ms, concurrency):
    """
    Определяй требования ДО начала разработки
    """
    spec = {
        'endpoint': endpoint,
        'target_qps': qps,
        'p50_latency_ms': latency_ms,
        'p99_latency_ms': latency_ms * 5,
        'concurrent_users': concurrency,
        'error_rate': '< 0.1%'
    }
    
    # Пример
    spec = performance_spec(
        endpoint='GET /api/users',
        qps=1000,
        latency_ms=50,
        concurrency=100
    )
    
    # Используй это для нагрузного тестирования
    # locust, k6, Apache JMeter

# Нагрузное тестирование
from locust import HttpUser, task, between

class UserBehavior(HttpUser):
    wait_time = between(1, 5)
    
    @task
    def get_users(self):
        self.client.get('/api/users')

Мои рекомендации

  • Профилируй первым — не гадай, измеряй
  • Big O notation важна — O(n) vs O(n²) разница в миллионы раз
  • Выбирай структуры данных правильно — set vs list это 1000x
  • Батчинг > индивидуальные операции — всегда
  • Кеширование это твой друг — но будь осторожен с инвалидацией
  • Генераторы вместо list — когда можешь
  • Не оптимизируй ранновремя — сначала пиши понятный код
  • Но проектируй с учётом перформанса — архитектура важна
  • Тестируй нагрузку — не угадаешь
  • Monitoring в production — иначе не узнаешь что медленно
Как писать высокопроизводительный код? | PrepBro