反向正则表达式模式比原始模式慢得多
Reversed regex pattern much slower than original
我有一个相当复杂的正则表达式搜索,最近我不得不将其扩展为“反向”模式。这很容易实现,但性能大约下降了 10 倍。
如果有任何关于如何改善此问题的提示,我将不胜感激,但我并不特别在意如何改善。例如,如果这比单一的、非常复杂的步骤更快,则可以使用两个或更多步骤。
原图
import regex
expression = regex.compile(
fr"""
(?:{CUE}) # cue before targets. non capture (?:)
(?:{PADDING}) # text before the match.
(?:
(?:{ITEM_SEPARATION})? # none or once
(?P<targets>{targets_as_regex})
)+
""",
regex.VERBOSE,
)
需要知道:CUE
是一个相当短的选项列表,例如:
CUE = r"""word
|two\swords
|simple\soptions?
"""
大概有5-15个选项,比较简单(逆向的场景稍微简单一点)。
PADDING
实际上是任何值,但不超过 150。(PADDING = r".{0,150}?"
)。在相反的情况下它有点限制,只有 [,A-Za-z\d\s]
ITEM_SEPARATION
很简单:可选空格后跟逗号、带界的“y”和更多可选空格:
ITEM_SEPARATION = r"""
\s* # optional spaces
(?:,|\by\b) # non-capture, either comma, or 'y' (with bounds)
\s* # optional spaces
"""
最后,问题可能出在targets_as_regex
,这是一个很大的有界名称列表。这很容易有成百上千个选项,尽管每次调用都会传递一个唯一的目标列表。
这个想法是匹配类似这样的东西:
This is a text, with a cue, then a bit more text and a match, other match y final match. And after the "." we stop matching.
而且表现足够好。但后来我们遇到了相反的情况:
反转模式
思路是这样搭配的:
This not a match, because of the stop. This is a match, another match y final match, because now we find a cue. Etc.
我自然地回收了我的图案:
expression = regex.compile(
fr"""
(
(?P<targets>{targets_as_regex})
(?:{ITEM_SEPARATION})?
)+
(?:{SIMPLE_PADDING}) # text before the match
(?:{CUE_AFTER}) # cue after targets
""",
regex.VERBOSE,
)
它按预期工作,但当目标(很多)出现在提示(很少)之前时,性能非常差。
我试过的
我添加了一个基本检查,如果提示根本不在文本中,我们将忽略整个内容,但这并没有太大帮助。我测试过的另一个想法是搜索目标的所有匹配项(不管提示和所有这些),然后将仅包含这些匹配项的缩短列表 (targets_as_regex
) 传递给慢速模式。这实际上有帮助,但帮助不大(仍然接近 10 倍的性能下降)。
还有其他有用的想法吗?
((?P<targets>{targets_as_regex})(?:{ITEM_SEPARATION})?)+
是well-knowncatastrophic backtracking俯卧模式(类似于(a+b?)+
缩减为(a+)+
),越往左的模式,越危险。
您需要将 (a+b?)+
等模式“展开”到 a+(?:ba+)*
中,使后续子模式仅在字符串内的不同位置匹配。
您需要的图案是
fr"""(?:{targets_as_regex})(?:{ITEM_SEPARATION}(?:{targets_as_regex}))*(?:{SIMPLE_PADDING})(?:{CUE_AFTER})"""
要加快速度,您需要一个从 targets_as_regex
构建的正则表达式 trie,请参阅 。
我有一个相当复杂的正则表达式搜索,最近我不得不将其扩展为“反向”模式。这很容易实现,但性能大约下降了 10 倍。
如果有任何关于如何改善此问题的提示,我将不胜感激,但我并不特别在意如何改善。例如,如果这比单一的、非常复杂的步骤更快,则可以使用两个或更多步骤。
原图
import regex
expression = regex.compile(
fr"""
(?:{CUE}) # cue before targets. non capture (?:)
(?:{PADDING}) # text before the match.
(?:
(?:{ITEM_SEPARATION})? # none or once
(?P<targets>{targets_as_regex})
)+
""",
regex.VERBOSE,
)
需要知道:CUE
是一个相当短的选项列表,例如:
CUE = r"""word
|two\swords
|simple\soptions?
"""
大概有5-15个选项,比较简单(逆向的场景稍微简单一点)。
PADDING
实际上是任何值,但不超过 150。(PADDING = r".{0,150}?"
)。在相反的情况下它有点限制,只有 [,A-Za-z\d\s]
ITEM_SEPARATION
很简单:可选空格后跟逗号、带界的“y”和更多可选空格:
ITEM_SEPARATION = r"""
\s* # optional spaces
(?:,|\by\b) # non-capture, either comma, or 'y' (with bounds)
\s* # optional spaces
"""
最后,问题可能出在targets_as_regex
,这是一个很大的有界名称列表。这很容易有成百上千个选项,尽管每次调用都会传递一个唯一的目标列表。
这个想法是匹配类似这样的东西:
This is a text, with a cue, then a bit more text and a match, other match y final match. And after the "." we stop matching.
而且表现足够好。但后来我们遇到了相反的情况:
反转模式
思路是这样搭配的:
This not a match, because of the stop. This is a match, another match y final match, because now we find a cue. Etc.
我自然地回收了我的图案:
expression = regex.compile(
fr"""
(
(?P<targets>{targets_as_regex})
(?:{ITEM_SEPARATION})?
)+
(?:{SIMPLE_PADDING}) # text before the match
(?:{CUE_AFTER}) # cue after targets
""",
regex.VERBOSE,
)
它按预期工作,但当目标(很多)出现在提示(很少)之前时,性能非常差。
我试过的
我添加了一个基本检查,如果提示根本不在文本中,我们将忽略整个内容,但这并没有太大帮助。我测试过的另一个想法是搜索目标的所有匹配项(不管提示和所有这些),然后将仅包含这些匹配项的缩短列表 (targets_as_regex
) 传递给慢速模式。这实际上有帮助,但帮助不大(仍然接近 10 倍的性能下降)。
还有其他有用的想法吗?
((?P<targets>{targets_as_regex})(?:{ITEM_SEPARATION})?)+
是well-knowncatastrophic backtracking俯卧模式(类似于(a+b?)+
缩减为(a+)+
),越往左的模式,越危险。
您需要将 (a+b?)+
等模式“展开”到 a+(?:ba+)*
中,使后续子模式仅在字符串内的不同位置匹配。
您需要的图案是
fr"""(?:{targets_as_regex})(?:{ITEM_SEPARATION}(?:{targets_as_regex}))*(?:{SIMPLE_PADDING})(?:{CUE_AFTER})"""
要加快速度,您需要一个从 targets_as_regex
构建的正则表达式 trie,请参阅