使用 python 和 javascript 的慢正则表达式,但在 go 和 php 中快速失败
Posted
技术标签:
【中文标题】使用 python 和 javascript 的慢正则表达式,但在 go 和 php 中快速失败【英文标题】:Slow regex with python and javascript but fast to fail in go and php 【发布时间】:2019-01-23 14:03:03 【问题描述】:我编写了一个正则表达式来解析 PostgreSQL 错误,以尝试向用户显示哪个字段有重复数据。 正则表达式是这样的:
^DETAIL:.[^\(]+.(.[^\)]+).[^\(]+.(.[^\)]+). already exists
如果您针对正确的消息(https://regex101.com/r/GZuREV/1)运行它会非常快:
ERROR: duplicate key value violates unique constraint "uq_content_block_internal_name_store_id"
DETAIL: Key (lower(internal_name::text), store_id)=(some content block-32067683, 0c6d20a7-d843-44f3-af9c-4a2cf2a47e4c) already exists.
但如果 PostgreSQL 发出另一条类似以下的消息,python 将需要大约 30 秒才能在我的机器中回复 (https://regex101.com/r/GZuREV/2)。
ERROR: null value in column "active" violates not-null constraint
DETAIL: Failing row contains (2018-08-16 14:23:52.214591+00, 2018-08-16 14:23:52.214591+00, null, 6f6d1bc9-c47e-46f8-b220-dae49bd58090, bf24d26e-4871-4335-9f18-83c5a52f1b3a, Some Product-a1c03dde-2de9-401c-92d5-5c1500908984, "de_DE": "Fugit tempore voluptas quos est vitae.", "en_GB": "Qu..., "de_DE": "Fuga reprehenderit nobis reprehenderit natus magni es..., "de_DE": "Fuga provident dolorum. Corrupti sunt in tempore quae..., my-product-53077578, SKU-53075778, 600, 4300dc25-04e2-4193-94c0-8ee97b636739, 52553d24-6d1c-4ce6-89f9-4ad765599040, null, 38089c3c-423f-430c-b211-ab7a57dbcc13, 7d7dc30e-b06b-48b7-b674-26d4f705583b, null, , 0, null, 9980, 100, 1, 5).
如果转到 regex101 链接,您可以看到,如果您切换到不同的语言,如 php 或 go,它们都会很快返回,说没有找到匹配项,但如果您选择 python 或 javascript,您将获得超时。
我的快速脏修复是这样的:
match = 'already exists' in error_message and compiled_regex.search(error_message)
您认为这可能是什么原因造成的?在我达到我想要的数据之前,会不会是贪婪的运营商消费?
更新 1
在 python 中使用 re.IGNORECASE 会使其慢约 9 秒,因为它花费了太多时间来小写一些东西。
忽略大小写
没有忽略大小写
更新 2
玩弄我发现要让它工作并失败,一个简单的改变就会失败
^DETAIL:.[^\(]+?\((.[^\)]+?).[^\(]+?.(.[^\)]+?). already exists
^ just changing this to \) make it stop timing out
^DETAIL:.[^\(]+?\((.[^\)]+?)\)[^\(]+?.(.[^\)]+?). already exists
【问题讨论】:
如果我删除初始 ^ 或使用多行,它看起来会变得非常慢。 【参考方案1】:这是一个正则表达式怪物:)
为什么不拆分 2 个正则表达式?
-
检查
already exists
是否匹配(非常快)
使用现有的正则表达式^DET.[^\(]+.(.[^\)]+).[^\(]+.(.[^\)]+)
提取要显示的数据
这应该会大大加快您的代码速度。 (你甚至可以像我一样缩短 DETAIL)
【讨论】:
【参考方案2】:Package regexp
import "regexp"
包正则表达式实现正则表达式搜索。
接受的正则表达式的语法是一样的通用 Perl、Python 和其他语言使用的语法。更准确地说,它 是 RE2 接受的语法,描述于 https://golang.org/s/re2syntax,除了 \C。概览 语法,运行
go doc regexp/syntax
此包提供的正则表达式实现保证 与输入的大小成线性时间运行。 (这是一个属性不是 由常规的大多数开源实现保证 表达式。)有关此属性的更多信息,请参阅
http://swtch.com/~rsc/regexp/regexp1.html
或任何有关自动机理论的书。
按照设计,Go 正则表达式保证在输入大小上线性运行,这是一些其他正则表达式实现无法保证的属性。见Regular Expression Matching Can Be Simple And Fast。
【讨论】:
【参考方案3】:这并不是问题的真正答案,但我认为问题可能是贪婪的运营商。无论如何,我认为你应该让其中的一部分变得懒惰以快速失败。
我使用了这种模式,并且在 regex101 上的所有语言引擎上都可以:
^DETAIL:.+?\((.+)\).+?\((.+)\) already exists.
link to the matched error
link to the error with no match
【讨论】:
那个不错,我忘了+?可以让它变得懒惰,谢谢!【参考方案4】:TL;DR:使用这个:
^DETAIL:\s*+Key[^\(]++\((.+)\)[^\(]+\(([^\)]+)\) already exists
见matching example和non-matching one
解释:
首先,原始正则表达式似乎与整个键组不匹配,您停在lower(internal_name::text
,省略了组合键的一些列以及不平衡的括号。如果您像这样修改它,它应该可以捕获复合键。如果不应该这样做,请告诉我:
^DETAIL:.[^\(]+.(.+)\)[^\(]+.(.[^\)]+). already exists
仅仅通过改变这个,正则表达式是“可运行的”,但仍然很慢。
他的主要原因之一是这个[^\(]+
。它首先匹配到DETAIL: Failing row contains(space)
,然后与正则表达式的其余部分匹配。它不会匹配,所以它会回溯到少一个字符,直到 DETAIL: Failing row contains
并继续使用正则表达式的其余部分。它不会匹配,所以会回到DETAIL: Failing row contain
...等。
避免这种情况的一种方法是使用所有格量词。这意味着一旦你拿了东西,你就不能回去了。所以使用这个[^\(]++
而不是这个[^\(]+
(即:^DETAIL:.[^\(]++.(.+)\)[^\(]+.(.[^\)]+). already exists
)会使正则表达式的步数从28590减少到1290。
但你仍然可以改进它。如果您知道您需要的数据使用关键字key
,请使用它!这样,由于失败的示例中不存在它,它将使正则表达式很快失败(一旦它读取 DETAIL 和下一个单词)
所以如果你使用^DETAIL:\s*+Key[^\(]++.(.+)\)[^\(]+.(.[^\)]+). already exists
,现在只有 12 步。
如果您觉得使用 key
过于具体,您可以使用不那么通用的东西来尝试查找“不是‘失败’”。像这样:
^DETAIL:\s*+(?!Fail)[^\(]++.(.+)\)[^\(]+.(.[^\)]+). already exists
这样是17步。
最后,您可以调整匹配内容的正则表达式。
改变这个:
^DETAIL:\s*+Key[^\(]++.(.+)\)[^\(]+
. # <============= here, use \( instead
(.[^\)]+). already exists
通过这个:
^DETAIL:\s*+Key[^\(]++.(.+)\)[^\(]+\((.[^\)]+). already exists
这将步骤从 538 减少到 215,因为您减少了回溯。
然后,在删除几个无用的点并用\(
或\)
(个人喜好)替换一些(假定为括号)点后,您就有了最终的正则表达式:
^DETAIL:\s*+Key[^\(]++\((.+)\)[^\(]+\(([^\)]+)\) already exists
【讨论】:
以上是关于使用 python 和 javascript 的慢正则表达式,但在 go 和 php 中快速失败的主要内容,如果未能解决你的问题,请参考以下文章