Почему uncompiled, многократно использовались регулярные выражения, намного медленнее в Python 3?

Отвечая на этот вопрос (и прочитав этот ответ на аналогичный вопрос), я подумал, что Я знал, как Python кэширует регулярные выражения.

Но потом я подумал, что проверю его, сравнив два сценария:

  • единственная компиляция простого регулярного выражения, затем 10 приложений этого скомпилированного регулярного выражения.
  • 10 приложений некомпилированного регулярного выражения (где я ожидал бы немного худшую производительность, потому что регулярное выражение пришлось бы компилировать один раз, затем кэшировать, а затем искать в кеше 9 раз).

Однако результаты были ошеломляющими (в Python 3.3):

>>> import timeit
>>> timeit.timeit(setup="import re", 
... stmt='r=re.compile(r"\w+")\nfor i in range(10):\n r.search("  jkdhf  ")')
18.547793477671938
>>> timeit.timeit(setup="import re", 
... stmt='for i in range(10):\n re.search(r"\w+","  jkdhf  ")')
106.47892003890324

Это в 5,7 раза медленнее! В Python 2.7 все еще есть увеличение в 2,5 раза, что также больше, чем я ожидал.

Изменено ли кэширование регулярных выражений между Python 2 и 3? Документы, похоже, не предлагают этого.

Ответ 1

Код изменился.

В Python 2.7 кеш представляет собой простой словарь; если в нем хранится более _MAXCACHE элементов, весь кэш очищается перед сохранением нового элемента. Поиск в кэше требует только создания простого ключа и тестирования словаря, см. 2.7 реализацию _compile()

В Python 3.x кэш был заменен декоратором @functools.lru_cache(maxsize=500, typed=True). Этот декоратор выполняет гораздо больше работы и включает в себя блокировку потоков, настройку очереди LRU кеша и ведение статистики кеша (доступно через re._compile.cache_info()). См. 3.3.0 реализацию _compile() и functools.lru_cache().

Другие заметили такое же замедление и подали номер 16389 в систему отслеживания ошибок Python. Я ожидаю, что 3.4 снова будет намного быстрее; либо улучшена реализация lru_cache, либо модуль re снова перейдет в пользовательский кеш.

Обновление: с ревизией 4b4dddd670d0 (hg)/0f606a6 (git) изменение кэша было возвращено к простой версии, найденной в 3.1. Версии Python 3.2.4 и 3.3.1 включают эту версию.

С тех пор в Python 3.7 кеш шаблонов был обновлен до пользовательской реализации кеша FIFO на основе обычного dict (в зависимости от порядка вставки, в отличие от LRU, не учитывается, как недавно элементы уже в кеше были использованы при выселении).