Подтвердить что ты не робот

Регулярные выражения в Python неожиданно замедляются

Рассмотрим этот код Python:

import timeit
import re

def one():
        any(s in mystring for s in ('foo', 'bar', 'hello'))

r = re.compile('(foo|bar|hello)')
def two():
        r.search(mystring)


mystring="hello"*1000
print([timeit.timeit(k, number=10000) for k in (one, two)])
mystring="goodbye"*1000
print([timeit.timeit(k, number=10000) for k in (one, two)])

В принципе, я сравниваю два способа проверить наличие одной из нескольких подстрок в большой строке.

То, что я получаю здесь (Python 3.2.3), является следующим:

[0.36678314208984375, 0.03450202941894531]
[0.6672089099884033, 3.7519450187683105]

В первом случае регулярное выражение легко побеждает выражение any - регулярное выражение сразу находит подстроку, а any должно проверять всю строку пару раз, прежде чем оно попадет в правильную подстроку.

Но что происходит во втором примере? В случае, когда подстрока отсутствует, регулярное выражение на удивление медленное! Это меня удивляет, так как теоретически регулярное выражение должно переходить только через строку, а выражение any должно пройти по строке 3 раза. Что здесь не так? Есть ли проблема с моим регулярным выражением или регулярные выражения Python просто замедляются в этом случае?

4b9b3361

Ответ 1

Примечание для будущих читателей

Я думаю, правильный ответ заключается в том, что алгоритмы обработки строк Python действительно оптимизированы для этого случая, а модуль re на самом деле немного медленнее. То, что я написал ниже, верно, но, вероятно, не относится к простым регулярным выражениям, которые у меня есть в вопросе.

Оригинальный ответ

По-видимому, это не случайная случайность - модуль Python re действительно медленнее. Похоже, что он использует рекурсивный подход к обратному отслеживанию, когда ему не удается найти совпадение, в отличие от построения DFA и моделирования его.

Он использует подход backtracking, даже если в регулярном выражении нет обратных ссылок!

Это означает, что в худшем случае регулярные выражения Python принимают экспоненциальное, а не линейное время!

Это очень подробный документ, описывающий проблему: http://swtch.com/~rsc/regexp/regexp1.html

Я думаю, что этот график ближе к концу суммирует его кратко: graph of performance of various regular expression implementations, time vs. string length

Ответ 2

Моя коллега нашла библиотеку re2 (https://code.google.com/p/re2/)? Существует оболочка python. Это немного, чтобы установить на некоторых системах.

У меня была такая же проблема с некоторыми сложными регулярными выражениями и длинными строками - re2 значительно ускорило время обработки - от секунд до миллисекунды.

Ответ 3

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

Первый просто делает это:

Does f match h? No.
Does b match h? No.
Does h match h? Yes.
Does e match e? Yes.
Does l match l? Yes.
Does l match l? Yes.
Does o match o? Yes.
Done. Match found.

Вторая делает это:

Does f match g? No.
Does b match g? No.
Does h match g? No.
Does f match o? No.
Does b match o? No.
Does h match o? No.
Does f match o? No.
Does b match o? No.
Does h match o? No.
Does f match d? No.
Does b match d? No.
Does h match d? No.
Does f match b? No.
Does b match b? Yes.
Does a match y? No.
Does h match b? No.
Does f match y? No.
Does b match y? No.
Does h match y? No.
Does f match e? No.
Does b match e? No.
Does h match e? No.
... 999 more times ...
Done. No match found.

Я могу только рассуждать о различии между any и regex, но я предполагаю, что регулярное выражение работает медленнее, главным образом потому, что оно работает в очень сложном движке, как эффективная, как конкретная реализация (in).

В первой строке регулярное выражение найдет совпадение почти мгновенно, а any должно пройти цикл дважды, прежде чем что-либо найти.

Во второй строке, однако, any выполняет по существу те же шаги, что и регулярное выражение, но в другом порядке. Это указывает на то, что решение any выполняется быстрее, вероятно, потому, что оно проще.

Конкретный код более эффективен, чем общий код. Любые знания о проблеме можно использовать для оптимизации решения. Простой код предпочтительнее сложного кода.. По существу, регулярное выражение выполняется быстрее, когда шаблон будет находиться рядом с началом строки, но in выполняется быстрее, когда шаблон находится рядом с концом строки или не найден на всех.

Отказ от ответственности: я не знаю Python. Я знаю алгоритмы.

Ответ 4

У вас есть регулярное выражение, состоящее из трех регулярных выражений. Как вы думаете, что работает, если регулярное выражение не проверяет это три раза?:-) Там нет волшебства в вычислениях, вам все равно придется сделать три проверки.

Но regexp будет делать каждый три символа теста по символу, а метод "one()" проверяет всю строку для одного соответствия, прежде чем переходить на следующую.

В первом случае регулярное выражение намного быстрее, потому что вы проверяете строку, которая будет соответствовать последнему. Это означает, что one() нужно сначала просмотреть всю строку для "foo", затем для "bar", а затем для "hello", где она совпадает. Сначала перенесите "привет", а одно() и два() имеют почти такую ​​же скорость, что и первое совпадение, выполненное в обоих случаях.

Regexps - это гораздо более сложные тесты, чем "in", поэтому я ожидаю, что он будет медленнее. Я подозреваю, что эта сложность сильно возрастает, когда вы используете "|", но я не читал источник для библиотеки regexp, так что я знаю.: -)