Files
drhider/History/code-fixes/2026-08-19-replacer-optimization.md
T

2.6 KiB
Raw Blame History

v0.0.39 — оптимизация apply_replacements (однопроходная замена) — 2026-08-19

Дата: 2026-08-19 Версия: 0.0.38 → 0.0.39 Файл: drhider/replacer.py, site/app.py (версия)


Суть

Обфускация PDF 19 МБ шла 132.7с, из них ИИ (LLM) всего 7с — остальное (~125с) тратил основной код. Замер выявил узкое место: apply_replacements.

Причина

apply_replacements для каждой сущности из mapping делала 2 полных прохода re.sub по всему тексту (основной + фоллбэк _md_tolerant_pattern). Сложность O(N_сущностей × размер_текста × 2) — квадратичная по числу сущностей. При 300+ сущностей и тексте в несколько МБ — десятки секунд.

Замер (0.9 МБ, 304 сущности): 17.3 с.

Решение

Однопроходная замена:

  • Все ключи собираются в один regex-паттерн через альтернацию (?:...|...) (по убыванию длины, с re.escape, границы (?<!\w)...(?!\w) для alnum-сущностей).
  • Один re.sub с callback (lookup в mapping), O(text).
  • Фоллбэк _md_tolerant_pattern применяется только к ключам, не найденным основной заменой (отслеживание через matched_keys), а не ко всем.

Результат замера

  • 300 сущностей реально в тексте (1.3 МБ): 1.17 с (было бы десятки сек).
  • Реалистичный сценарий (10 сущностей): 0.29 с.
  • Согласованность токенов и mapping.csv сохранены (проверено через obfuscate).

Проверка

  • test_replacer 10/10, test_builder 20/20, test_scanner 20/20, test_zip 28/28.
  • Пайплайн obfuscate: «ООО Ромашка»→«Вектор_0001» в двух файлах согласовано, mapping.csv корректен.

Примечание

  • scan_regex (0.4с) и upload (сеть ~1.7 МБ/с) — не узкие места, не трогали.
  • pdf_to_markdown (pdfplumber) локально не замерен (нет библиотеки); остаётся потенциальным вторым узким местом для будущей оптимизации (pypdf/pdfminer).