Разница в производительности re.match vs re.search

Я попытался сравнить re.match и re.search с модулем timeit, и я обнаружил, что совпадение было лучше, чем поиск, когда строка, которую я хочу найти, была в начале строки.

>>> s1 = '''
... import re
... re.search(r'hello','helloab'*100000)
... '''
>>> timeit.timeit(stmt=s1,number=10000)
32.12064480781555


>>> s = '''
... import re
... re.match(r'hello','helloab'*100000)
... '''
>>> timeit.timeit(stmt=s,number=10000)
30.9136700630188

Теперь я знаю, что match ищет шаблон в начале строки и возвращает объект, если он найден, но мне интересно, как работает поиск.

Выполняет ли поиск любое дополнительное соответствие после того, как строка найдена в начале, которая замедляет ее?

Обновление

После использования кода @David Robinsons я получил результаты, похожие на него.

>>> print timeit.timeit(stmt="r.match('hello')",
...              setup="import re; s = 'helloab'*100000; r = re.compile('hello')",
...              number = 10000000)
49.9567620754
>>> print timeit.timeit(stmt="r.search('hello')",
...              setup="import re; s = 'helloab'*100000; r = re.compile('hello')",
...             number = 10000000)
35.6694438457

Итак, теперь возникает вопрос: почему search работает match?

Ответы

Ответ 1

"Итак, обновленный вопрос теперь, почему поиск не работает?"

В этом конкретном случае, где используется буквальная строка, а не шаблон регулярного выражения, действительно re.search немного быстрее, чем re.match для реализации CPython по умолчанию (я не тестировал это в других воплощениях Python).

>>> print timeit.timeit(stmt="r.match(s)",
...              setup="import re; s = 'helloab'*100000; r = re.compile('hello')",
...              number = 10000000)
3.29107403755
>>> print timeit.timeit(stmt="r.search(s)",
...              setup="import re; s = 'helloab'*100000; r = re.compile('hello')",
...             number = 10000000)
2.39184308052

Заглянув в C-код за этими модулями, похоже, что в коде поиска есть встроенная оптимизация чтобы быстро совместить шаблоны с префиксом со строкой. В приведенном выше примере весь шаблон представляет собой литеральную строку без шаблонов регулярных выражений, и поэтому оптимизированная подпрограмма используется для соответствия всему шаблону.

Обратите внимание, как производительность ухудшается, когда мы вводим символы регулярных выражений и, поскольку префикс литеральной строки становится короче:

>>> print timeit.timeit(stmt="r.search(s)",
...              setup="import re; s = 'helloab'*100000; r = re.compile('hell.')",
...             number = 10000000)

3.20765399933
>>> 
>>> print timeit.timeit(stmt="r.search(s)",
...              setup="import re; s = 'helloab'*100000; r = re.compile('hel.o')",
...             number = 10000000)
3.31512498856
>>> print timeit.timeit(stmt="r.search(s)",
...              setup="import re; s = 'helloab'*100000; r = re.compile('he.lo')",
...             number = 10000000)
3.31983995438
>>> print timeit.timeit(stmt="r.search(s)",
...              setup="import re; s = 'helloab'*100000; r = re.compile('h.llo')",
...             number = 10000000)
3.39261603355

Для части шаблона, содержащего шаблоны регулярных выражений, SRE_MATCH используется для определения совпадений. Это по существу тот же код, что и за re.match.

Обратите внимание, как результаты близки (с re.match чуть быстрее), если шаблон начинается с шаблона регулярного выражения вместо строковой строки.

>>> print timeit.timeit(stmt="r.match(s)",
...              setup="import re; s = 'helloab'*100000; r = re.compile('.ello')",
...              number = 10000000)
3.22782492638
>>> print timeit.timeit(stmt="r.search(s)",
...              setup="import re; s = 'helloab'*100000; r = re.compile('.ello')",
...             number = 10000000)
3.31773591042

Другими словами, игнорируя тот факт, что search и match имеют разные цели, re.search быстрее, чем re.match, только когда шаблон является литеральной строкой.

Конечно, если вы работаете с буквальными строками, вам, скорее всего, будет лучше использовать строковые операции.

>>> # Detecting exact matches
>>> print timeit.timeit(stmt="s == r", 
...              setup="s = 'helloab'*100000; r = 'hello'", 
...              number = 10000000)
0.339027881622

>>> # Determine if string contains another string
>>> print timeit.timeit(stmt="s in r", 
...              setup="s = 'helloab'*100000; r = 'hello'", 
...              number = 10000000)
0.479326963425


>>> # detecting prefix
>>> print timeit.timeit(stmt="s.startswith(r)",
...              setup="s = 'helloab'*100000; r = 'hello'",
...             number = 10000000)
1.49393510818
>>> print timeit.timeit(stmt="s[:len(r)] == r",
...              setup="s = 'helloab'*100000; r = 'hello'",
...             number = 10000000)
1.21005606651

Ответ 2

На моей машине (Python 2.7.3 на Mac OS 10.7.3, 1,7 ГГц Intel Core i5), когда было сделано построение строки, импорт ре и компиляция регулярных выражений в настройках и выполнение 10000000 итераций вместо 10, Я нахожу противоположное:

import timeit

print timeit.timeit(stmt="r.match(s)",
             setup="import re; s = 'helloab'*100000; r = re.compile('hello')",
             number = 10000000)
# 6.43165612221
print timeit.timeit(stmt="r.search(s)",
             setup="import re; s = 'helloab'*100000; r = re.compile('hello')",
            number = 10000000)
# 3.85176897049