У меня медленное регулярное выражение Python, если я просто удалю последнюю строку регулярного выражения, скорость увеличится на два порядка! Вот мой воспроизводящий пример:

import re
import timeit

mystr = "14923KMLI MLI2010010206581258   0.109 b                              M     M     M     0    09 60+              "

basere = r"""
(?P<wban>[0-9]{5})
(?P<faaid>[0-9A-Z]{4})\s
(?P<id3>[0-9A-Z]{3})
(?P<tstamp>[0-9]{16})\s+
\[?\s*((?P<vis1_coef>\-?\d+\.\d*)|(?P<vis1_coef_miss>M))\s*\]?\s*
\[?(?P<vis1_nd>[0-9A-Za-z\?\$/ ])\]?\s+
((?P<vis2_coef>\d+\.\d*)|(?P<vis2_coef_miss>[M ]))\s+(?P<vis2_nd>[A-Za-z\?\$ ])\s+
...............\s+
\[?\s*((?P<drct>\d+)|(?P<drct_miss>M))\s+
((?P<sknt>\d+)|(?P<sknt_miss>M))\s+
((?P<gust_drct>\d+)\+?|(?P<gust_drct_miss>M))\s*\]?\s+
"""
additional = r"""
\[?((?P<gust_sknt>\d+)R?L?F*\d*\+?|(?P<gust_sknt_miss>M))\s*\]?\s+
"""

P1_RE = re.compile(basere + additional, re.VERBOSE)
P2_RE = re.compile(basere, re.VERBOSE)


for myre in ["P1_RE", "P2_RE"]:
    statement = "%s.match('%s')" % (myre, mystr)
    res = timeit.timeit(statement, "from __main__ import %s" % (myre,), 
                        number=1000)
    print('%s took %.9f per iteration' % (myre, res / 1000.))

# result on my laptop, python 2.6 and 3.3 tested
# P1_RE took 0.001489143 per iteration
# P2_RE took 0.000019991 per iteration

Таким образом, единственная разница между P1_RE и P2_RE - это дополнительное регулярное выражение. Есть идеи относительно того, что я делаю неправильно?

1
daryl 15 Авг 2014 в 00:51
6
Можете ли вы уменьшить объем регулярного выражения и воспроизвести поведение?
 – 
Daenyth
15 Авг 2014 в 00:53
Я не уверен в том, что вы меня просите. Я попытался уменьшить размер регулярного выражения, но основное изменение производительности происходит только с проиллюстрированной разницей.
 – 
daryl
15 Авг 2014 в 01:00
3
Уменьшите регулярное выражение до /trivial/ vs /trivial<withAdditional>/ и снова проверьте разницу. Как написано, ваш код не поддается расшифровке, и вы вряд ли получите большую помощь
 – 
Daenyth
15 Авг 2014 в 01:02
Эти регулярные выражения чрезвычайно трудно анализировать, не зная заранее, что они намереваются сопоставить.
 – 
chepner
15 Авг 2014 в 01:04
2
Учитывая большое количество необязательных элементов, присутствующих во второй половине или около того, кажется, есть много возможностей для возврата в этом регулярном выражении. Добавление большего количества в конец может вызвать экспоненциальное поведение в движке (т.е. строка не совпадает, но есть еще много способов, чтобы она не совпадала, которые необходимо проверить, прежде чем мы сможем быть уверены.)
 – 
chepner
15 Авг 2014 в 01:16

1 ответ

Лучший ответ

Это из-за возврата; что является важной причиной неэффективности регулярных выражений.

Последняя удаленная строка требует сопоставления хотя бы одной цифры или M с последующими пробелами и многих других необязательных вещей по пути.

Предпоследняя строка:

((?P<gust_drct>\d+)\+?|(?P<gust_drct_miss>M))\s*\]?\s+

Изначально соответствует последней части, то есть 60+, и последующим пробелам, ничего не оставляя для последней строки. Таким образом, регулярное выражение не может сопоставить и выполняет возврат по одному символу за раз, чтобы попытаться сопоставить последнюю строку. Дело в том, что есть много вещей, которые регулярное выражение пытается сопоставить, и для каждого символа, для которого регулярное выражение было выполнено с возвратом, есть все больше и больше попыток сопоставления.

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


Простым примером может служить строка (10 а):

aaaaaaaaaa

И регулярное выражение:

a+a+a+a+a+a+a+a+a+a+

Первая часть регулярного выражения a+ будет соответствовать всей строке (потому что + жадная), но затем ей нужно соответствовать большему количеству a, потому что следующая часть регулярного выражения a+. Затем движок выполняет возврат одного символа, так что первый a+ соответствует aaaaaaaaa (9 a), а второй соответствует последнему a.

Далее идет третий a+, и поскольку больше нет a для сопоставления, регулярное выражение вернет один символ назад. Поскольку второй a не может выполнить возврат, это будет первый a+, который соответствует и соответствует aaaaaaaa (8 а), тогда второй a+ будет соответствовать последнему aa. Теперь больше нет a, поэтому второй a+ вернется, чтобы соответствовать предпоследнему a, и, наконец, третий a+ может соответствовать последнему a.

Посмотрите, сколько сделали первые 3 a+? А теперь представьте, что вы перебираете все возможные совпадения, пока все не совпадет?


Обычно, если вам вообще не нужен возврат с возвратом, вы должны использовать атомарные группы и притяжательные квантификаторы, но python не поддерживает их, по крайней мере, не модуль re (regex поддерживает).

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

4
Jerry 15 Авг 2014 в 01:24
Спасибо. Ваш пост направляет меня в правильном направлении. Первоначальный удар заключался в том, чтобы удалить любой "[" "]" из входной строки перед запуском регулярного выражения и удалить все эти необязательные "[?" "]?" из регулярного выражения. Это уже сильно ускорило работу.
 – 
daryl
15 Авг 2014 в 01:41