【问题标题】:Regex IsMatch really slow when doing a single character wildcard search进行单个字符通配符搜索时,Regex IsMatch 真的很慢
【发布时间】:2022-01-23 08:05:31
【问题描述】:

我们遇到这样一种情况,即在开始时使用单个字符进行通配符搜索,然后在通配符之后使用其他字符,并且运行速度非常慢(至少在 c# 中)。 有没有这样做的原因和改进的方法?在几乎所有其他情况下它都更快。

20k 长随机字符串的示例运行 1000 次:

  • a.*r1 耗时:1802
  • r1.*耗时:9
  • r1.*b.*c 耗时:9
  • r1f.*b.*c 耗时:16
  • a.*r1f.*c 耗时:3199
  • a.*r1.*c 耗时:1895
  • a.*b.*r1f 耗时:55450

它绝对不是随机字符串,因为尝试了不同的字符串。

模式肯定是,如果第一部分是单个字符,然后是通配符后面的任何字符,它总是慢得多。

--更新--

我想知道 Regex 的工作方式是否是循环查找该单个字符,当它找到它时,它会一直搜索直到结束寻找下一个模式。当它没有找到它时,它会返回第一个字符并开始寻找下一个第一个字符,直到它再次找到第一个匹配并执行一些完整的逻辑,即使它可以跳过它在第一次运行时传递的所有那些字符.

我想我已经通过生成一个不带字符“a”的随机字符串来确认这一点——如果我使用这个字符作为第一个字符,它真的很快,但如果我使用“c”它很慢。即 a.*b.*r1f 在这种情况下是即时的,但 c.*b.*r1f 需要很长时间。

如果想知道您是否可以在正则表达式中以某种方式对其进行优化?

【问题讨论】:

  • 您没有在其中任何一个中进行单字符通配符搜索。单个字符通配符搜索将只是.(如果它是可选的.?),而不是.*(匹配任何字符零次或更多次)。
  • 没错 - 它不是一个字符。实际上,我们正在寻找 [介于两者之间的任何东西] 然后 r1(在第一个示例中)
  • 那不应该是一个非贪婪的通配符搜索.*?吗?
  • 也许这就是答案 - 我真的不知道这意味着什么 - 我真的会这样做。*?而不是 .*
  • 好吧,我不知道它是否会解决速度问题,但是您的正则表达式似乎无法正确实现您的目标。 .* test.*? test.

标签: c# regex performance


【解决方案1】:

造成这种性能差异的原因在于优化搜索的方式。

当模式以文字字符开头时,在正则表达式引擎的“正常遍历”之前使用快速算法来查找字符串中模式可能成功的可能位置(文字子字符串所在的位置)。然后,正则表达式引擎仅在这些位置测试模式。

这就是为什么以字母 a 开头的模式,对于不包含字母 'a' 的字符串(无论大小)被快速解决(不匹配,整个模式从未测试过)。

现在为什么对于同一种模式,一个以字母a(只有一个文字字符)开头的模式和一个以abcd 开头的模式在大多数情况下会在随机字符串中提供不同的性能。答案很简单,四个字符abcd 的位置比只有a 的位置少。尝试更少的位置 => 更快的结果。


还要注意,像a.*b.*c 这样的模式被称为病态模式,因为它可能导致回溯步骤数量的潜在爆炸。如果使用非贪婪量词有时可能会减少问题,则不能保证它总是能提高性能(它不是魔杖)。最好的方法始终是使用适当的字符类、适当的量词和最准确的字符串描述,尽可能地避免.*.*?。例如a[^b]*b[^c]*c

【讨论】:

  • 谢谢你-看来我的假设可能是正确的。感谢您的输入 - 正如您所做的那样,您确实加快了单个字符的速度。但是对于多个字符,它似乎不起作用 - 即“g[^od]*?od”不返回任何匹配“goodod”
  • "g[^od]*?od" 没有返回“goodod”的任何匹配项 - 确实;为什么会呢? “g 后跟零个或多个除 o 或 d 之外的任何内容,后跟 od”不描述 goodod
  • @tank104:它什么也不返回,因为字符类是一组字符(没有任何顺序)仅此而已,仅此而已,您不能将多个字符的子字符串放入其中并期望它会禁止子字符串。不,[^od] 匹配所有不是do 的字符。要在god 之间禁止od,模式为g[^o]*(?>o(?!d)[^o]*)*od
  • 但首先,您必须很好地了解量词的工作原理以及回溯是什么。这真的是最重要的。
  • 谢谢-我想我现在了解回溯(以及为什么 ? 在 .? 中很重要以限制它。现在也了解为什么 [^od] 不起作用,因为正如您提到的它是一组字符,没有以任何方式排序。尽管试图最好地理解您的解决方案。如果我要将示例更改为 3 个(或更多)字符搜索,即 good1od12 中的 god12 会是 g[ ^o]*(?>o(?!d12)[^o]*)*od12 是一种有效的方法吗?(它的工作速度很快,现在只需要以编程方式在代码中生成它)跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-07-08
  • 1970-01-01
  • 2015-02-26
  • 1970-01-01
  • 2019-11-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多