Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Оптимизация и производительность
optimization

Оптимизация и производительность

Профилирование, кэширование, типичные проблемы и техники оптимизации

Оптимизация и производительность

Производительность регулярных выражений критична при обработке больших объёмов данных. В этой теме изучим техники оптимизации и профилирования.

#Профилирование regex

#Измерение времени выполнения

import re import time pattern = re.compile(r'\d+') text = 'abc123def456' * 1000 start = time.perf_counter() for _ in range(1000): pattern.findall(text) end = time.perf_counter() print(f'Время: {end - start:.4f} сек')

#Сравнение паттернов

import timeit # Паттерн 1: жадный pattern1 = r'.*<div>.*' # Паттерн 2: нежадный pattern2 = r'.*?<div>.*?' # Паттерн 3: конкретный pattern3 = r'[^<]*<div>[^<]*' text = '<div>content</div>' * 100 time1 = timeit.timeit(lambda: re.search(pattern1, text), number=1000) time2 = timeit.timeit(lambda: re.search(pattern2, text), number=1000) time3 = timeit.timeit(lambda: re.search(pattern3, text), number=1000) print(f'Жадный: {time1:.4f}') print(f'Нежадный: {time2:.4f}') print(f'Конкретный: {time3:.4f}')

#Кэширование компиляции

Модуль re автоматически кэширует до 512 скомпилированных паттернов:

# Автоматическое кэширование for i in range(100): re.search(r'\d+', text) # Компилируется один раз, затем из кэша # Явная компиляция для важных паттернов pattern = re.compile(r'\d+') for i in range(100): pattern.search(text) # Из кэша

#Проверка кэша

import re # Размер кэша (по умолчанию 512) print(re._cache.__len__()) # Очистка кэша re.purge()

#Техники оптимизации

#1. Избегайте универсальных классов

# Плохо: .соответствует любой символ pattern = r'.*<div>.*' # Хорошо: конкретный класс pattern = r'[^<]*<div>[^<]*'

#2. Используйте якоря

# Плохо: поиск по всей строке pattern = r'\d{4}-\d{2}-\d{2}' # Хорошо: с якорями, если известно положение pattern = r'^\d{4}-\d{2}-\d{2}$'

#3. Конкретизируйте квантификаторы

# Плохо: неограниченный квантификатор pattern = r'.*' # Хорошо: с ограничением pattern = r'.{0,100}' # Или: конкретный класс pattern = r'[^\n]{0,100}'

#4. Избегайте вложенных квантификаторов

# Плохо: catastrophic backtracking pattern = r'(a+)+b' # Хорошо: упрощённый pattern = r'a+b'

#5. Используйте non-capturing группы

# Плохо: захватывающие группы, если не нужны pattern = r'(\d+)-(\d+)-(\d+)' # Хорошо: non-capturing pattern = r'(?:\d+)-(?:\d+)-(?:\d+)'

#6. Компиляция для повторного использования

# Плохо: компиляция при каждом вызове def find_emails(text): return re.findall(r'[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}', text) # Хорошо: компиляция один раз EMAIL_PATTERN = re.compile(r'[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}') def find_emails(text): return EMAIL_PATTERN.findall(text)

#7. Раннее завершение с якорями

# Плохо: поиск по всей строке if re.search(r'^ERROR', log_line): process(log_line) # Хорошо: startswith быстрее для простых префиксов if log_line.startswith('ERROR'): process(log_line)

#8. Оптимизация alternation (|)

# Плохо: частые варианты в конце pattern = r'cat|dog|bird|fish|elephant' # Хорошо: частые варианты в начале pattern = r'elephant|fish|bird|dog|cat' # Или: сортировка по длине (длинные primero) pattern = r'elephant|bird|fish|cat|dog'

#Оптимизация для больших данных

#Обработка по частям

def process_large_file(filename, chunk_size=8192): pattern = re.compile(r'\d+') with open(filename, 'r') as f: buffer = '' while True: chunk = f.read(chunk_size) if not chunk: break buffer += chunk lines = buffer.split('\n') buffer = lines[-1] # Оставляем последнюю неполную строку for line in lines[:-1]: matches = pattern.findall(line) process(matches)

#Использование finditer

# Плохо: создаёт весь список в памяти matches = re.findall(r'\d+', large_text) for match in matches: process(match) # Хорошо: итератор for match in re.finditer(r'\d+', large_text): process(match.group())

#Параллельная обработка

from concurrent.futures import ProcessPoolExecutor def process_chunk(args): text, pattern = args return re.findall(pattern, text) def parallel_findall(text, pattern, num_chunks=4): chunk_size = len(text) // num_chunks chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)] with ProcessPoolExecutor() as executor: results = executor.map(process_chunk, [(c, pattern) for c in chunks]) return [match for chunk_results in results for match in chunk_results]

#Распространённые проблемы

#1. Медленный поиск с .*

# Плохо: .*соответствует до конца строки text = 'a' * 10000 + 'b' re.search(r'a*b', text) # Медленно # Хорошо: конкретный класс re.search(r'[a]*b', text) # Быстрее

#2. Избыточные группы

# Плохо: лишние группы pattern = r'(\d{4})-(\d{2})-(\d{2})' # Если группы не нужны: pattern = r'(?:\d{4})-(?:\d{2})-(?:\d{2})'

#3. Неправильный порядок alternation

# Плохо: редкие варианты primero pattern = r'zebra|elephant|cat|dog' # Хорошо: частые варианты primero pattern = r'cat|dog|elephant|zebra'

#Инструменты для оптимизации

#re.debug

import re # Отладка скомпилированного паттерна pattern = re.compile(r'\d+') print(pattern.pattern) # Исходный паттерн

#Визуализация на regex101

  1. Откройте regex101.com
  2. Вставьте паттерн и текст
  3. Выберите flavour "Python"
  4. Изучите объяснение и шаги выполнения

#Профилирование с cProfile

import cProfile import re def benchmark(): pattern = re.compile(r'\d+') text = 'abc123def456' * 1000 for _ in range(1000): pattern.findall(text) cProfile.run('benchmark()')

#Практические рекомендации

#1. Предварительная компиляция

# Модуль уровня EMAIL_PATTERN = re.compile(r'^[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}$') PHONE_PATTERN = re.compile(r'^\+7\d{10}$') def validate_email(email): return bool(EMAIL_PATTERN.match(email))

#2. Ленивая компиляция

class PatternCache: _cache = {} @classmethod def get(cls, pattern): if pattern not in cls._cache: cls._cache[pattern] = re.compile(pattern) return cls._cache[pattern] # Использование pattern = PatternCache.get(r'\d+')

#3. Бenchmark перед оптимизацией

import timeit # Измерьте текущую производительность baseline = timeit.timeit(lambda: current_pattern.findall(text), number=1000) # Измерьте оптимизированную optimized = timeit.timeit(lambda: new_pattern.findall(text), number=1000) print(f'Улучшение: {baseline / optimized:.2f}x')