2个数量级的性能变化与看似小的python正则表达式的变化

时间:2014-08-14 20:51:42

标签: python regex performance

我有一个慢速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_REP2_RE之间的唯一区别是额外的正则表达式。关于我做错了什么想法?

1 个答案:

答案 0 :(得分:4)

这是因为回溯;这是正则表达式效率低下的一个重要原因。

您删除的最后一行要求至少匹配1个数字或M,后跟空格,以及许多可选项。

倒数第二行:

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

最初匹配最后一部分,即60+和后面的空格,不留最后一行。因此,正则表达式无法匹配并一次回溯一个字符以尝试匹配最后一行。事实上,正则表达式会尝试匹配很多东西,对于正则表达式回溯的每个字符,有越来越多的匹配尝试。

在正则表达式匹配之前,它实际上拒绝了之前正则表达式之前匹配的很多字符。


一个简单的例子是字符串(10 a):

aaaaaaaaaa

正则表达式:

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

正则表达式a+的第一部分将匹配整个字符串(因为+是贪婪的),但是,它需要匹配更多a,因为正则表达式的下一部分是a+。引擎然后回溯一个字符,以便第一个a+匹配aaaaaaaaa(9个),第二个匹配最后一个a

接下来还有第三个a+,因为不再需要a匹配,正则表达式将回溯一个字符。由于第二个a无法回溯,因此第一个a+会匹配aaaaaaaa(8个),然后第二个a+将与最后一个{{1}匹配}}。现在不再有aa,因此第二个a将回溯以匹配倒数第二个a+,最后,第三个a可以匹配最后一个a+ }。

看看前3 a做了多少?现在想象一下,通过每一个可能的匹配,直到所有匹配?


通常,如果你根本不想回溯,你会使用原子组和占有量词,但是python不支持那些,至少不支持a+模块(regex然而,模块确实如此。

你可能想要修改你的正则表达式并试着看看它应该真正匹配什么,什么是真正可选的,特别是你的捕获组中可能的空格。