【问题标题】:C# Regex Performance very slowC# 正则表达式性能非常慢
【发布时间】:2016-09-13 12:47:54
【问题描述】:

我是正则表达式主题的新手。我想用以下正则表达式解析日志文件:

(?<time>(.*?))[|](?<placeholder4>(.*?))[|](?<source>(.*?))[|](?<level>[1-3])[|](?<message>(.*?))[|][|][|](?<placeholder1>(.*?))[|][|](?<placeholder2>(.*?))[|](?<placeholder3>(.*))

日志行如下所示:

2001.07.13 09:40:20|1|SomeSection|3|====== Some log message::Type: test=sdfsdf|||.\SomeFile.cpp||60|-1

带有 appr 的日志文件。 3000 行需要很长时间来解析它。你有一些提示可以加快性能吗?谢谢...

更新: 我使用正则表达式是因为我使用了不同的日志文件,它们的结构不同,我就是这样使用它的:

string[] fileContent = File.ReadAllLines(filePath);
Regex pattern = new Regex(LogFormat.GetLineRegex(logFileFormat));

foreach (var line in fileContent)
{
   // Split log line
   Match match = pattern.Match(line);

   string logDate = match.Groups["time"].Value.Trim();
   string logLevel = match.Groups["level"].Value.Trim();
   // And so on...
}

解决方案:
谢谢你的帮助。我已经对其进行了测试,结果如下:

1.) 仅添加 RegexOptions.Compiled:
从 00:01:10.9611143 到 00:00:38.8928387

2.) 使用 Thomas Ayoub 正则表达式
从 00:00:38.8928387 到 00:00:06.3839097

3.) 使用 Wiktor Stribiżew 正则表达式
从 00:00:06.3839097 到 00:00:03.2150095

【问题讨论】:

  • 你为什么使用正则表达式?看来您可以只使用String.Split 来获取列数组。
  • 除非你真的需要选择这种类型的行(特定列中有数字),否则你真的可以使用String.Split()。否则,将所有.*? 替换为[^|]*,您还可以通过将[|][|][|] 替换为[|]{3} 获得更快的速度。实际上,即使您需要检查某个特定列的值是否为数字,您也可以使用非正则表达式的代码来做到这一点...
  • 您还没有向我们展示您实际上是如何匹配正则表达式的 - 请添加相关代码。
  • 哦,是的,正则表达式对象 - 如果使用非静态方法 - 应该在循环之外创建。
  • 字符类[|] 似乎是获取一个字符的低效方式。正则表达式引擎可能会将其优化为单个显式字符,但也可能不会。我会用\| 替换它们。

标签: c# regex


【解决方案1】:

让我将我的评论“转换”为答案,因为现在我看到了您可以对正则表达式的性能做些什么。

As I have mentioned above,将所有.*? 替换为[^|]*,并将所有重复的[|][|][|] 替换为[|]{3}(或类似的,取决于[|] 的数量。另外,不要使用嵌套捕获组,这也会影响性能!

var logFileFormat = @"(?<time>[^|]*)[|](?<placeholder4>[^|]*)[|](?<source>[^|]*)[|](?<level>[1-3])[|](?<message>[^|]*)[|]{3}(?<placeholder1>[^|]*)[|]{2}(?<placeholder2>[^|]*)[|](?<placeholder3>.*)";

只有最后一个 .* 可以保持“通配符”,因为它将抢占该行的其余部分。

这是您和我在RegexHero 的正则表达式模式的比较。

然后,使用RegexOptions.Compiled:

Regex pattern = new Regex(LogFormat.GetLineRegex(logFileFormat), RegexOptions.Compiled);

【讨论】:

  • 您也可以使用 RegexOptions.ExplicitCapture 代替删除嵌套的捕获组。
  • 这是有道理的,因为命名组将捕获相关细节。
【解决方案2】:

如果您多次使用相同的正则表达式,请确保编译它,以免每次都重新创建正则表达式。这可以产生多个数量级。

var regex = new Regex(".*", RegexOptions.Compiled);

以下 LinqPad 代码显示了 3 种使用正则表达式的方法,从最快到最慢。

regexFast 方法大约需要 5 秒,regexSlow 方法需要 6 秒,regexSlowest 大约需要 50 秒。

void Main()
{
    var sw = new Stopwatch();

    var regex = @"(?<first>T[he]{2})\s*\w{5}.*";

    // This is the fastest method.
    sw.Restart();
    var regexFast = new Regex(regex, RegexOptions.Compiled);
    for (int i = 0; i < 9999999; i++)
    {
        regexFast.Match("The quick brown fox");
    }
    sw.Stop();
    sw.ElapsedMilliseconds.Dump();

    // This is a little slower - we didn't compile the regex so it has 
    // to do some extra work on each iteration.
    sw.Restart();
    var regexSlow = new Regex(regex);
    for (int i = 0; i < 9999999; i++)
    {
        regexSlow.Match("The quick brown fox");
    }
    sw.Stop();
    sw.ElapsedMilliseconds.Dump();

    // This method is super slow - we create a new Regex each time, so 
    // we have to do *lots* of extra work.
    sw.Restart();
    for (int i = 0; i < 9999999; i++)
    {
        var regexSlowest = new Regex(regex);
        regexSlowest.Match("The quick brown fox");
    }
    sw.Stop();
    sw.ElapsedMilliseconds.Dump();
}

【讨论】:

【解决方案3】:

您的正则表达式可以优化为:

(?<time>([^|]*))[|](?<placeholder4>([^|]*))[|](?<source>([^|]*))[|](?<level>[1-3])[|](?<message>([^|]*))[|]{3}(?<placeholder1>([^|]*))[|][|](?<placeholder2>([^|]*))[|](?<placeholder3>([^|]*))

使用否定的 char 类而不是惰性量词。它减少了回溯。 Regex101 从 316 步变为 47 步进行此更改。结合RB.的答案,你应该没问题

【讨论】:

  • Compiled 可能会有所帮助,正如 RB 所说,但那些 .*?s 才是真正的问题。我会在使用它时摆脱嵌套组。
猜你喜欢
  • 1970-01-01
  • 2020-06-21
  • 2012-03-30
  • 2014-05-04
  • 2018-11-26
  • 2021-11-25
  • 2012-01-27
  • 1970-01-01
相关资源
最近更新 更多