【问题标题】:Dynamically Built Regular Expressions are running extremely slow!动态构建的正则表达式运行速度非常慢!
【发布时间】:2010-10-22 16:22:07
【问题描述】:

我通过运行一些 xml 结构并在遍历其节点类型时构建语句来动态生成正则表达式。我将此正则表达式用作我定义的布局类型的一部分。然后我解析一个文本文件,该文件在每行的开头都有一个 Id。这个 id 指向一个特定的布局。然后我尝试将该行中的数据与其正则表达式进行匹配。

听起来不错,不错吧?唯一的问题是匹配字符串非常慢。我将它们设置为已编译以尝试加快速度,但无济于事。令人费解的是,这些表达方式并没有那么复杂。我绝不是 RegEx 专家,但我体面了解他们以使事情顺利进行。

这是生成表达式的代码...

StringBuilder sb = new StringBuilder();
//get layout id and memberkey in there...
sb.Append(@"^([0-9]+)[ \t]{1,2}([0-9]+)"); 
foreach (ColumnDef c in columns)
{
    sb.Append(@"[ \t]{1,2}");
    switch (c.Variable.PrimType)
    {
        case PrimitiveType.BIT:
            sb.Append("(0|1)");
            break;
        case PrimitiveType.DATE:
            sb.Append(@"([0-9]{2}/[0-9]{2}/[0-9]{4})");
            break;
        case PrimitiveType.FLOAT:
            sb.Append(@"([-+]?[0-9]*\.?[0-9]+)");
            break;
        case PrimitiveType.INTEGER:
            sb.Append(@"([0-9]+)");
            break;
        case PrimitiveType.STRING:
            sb.Append(@"([a-zA-Z0-9]*)");
            break;
    }
}
sb.Append("$");
_pattern = new Regex(sb.ToString(), RegexOptions.Compiled);

实际慢的部分...

public System.Text.RegularExpressions.Match Match(string input)
{
    if (input == null)
       throw new ArgumentNullException("input");

    return _pattern.Match(input);
}

一个典型的“_pattern”可能有大约 40-50 列。我将从粘贴整个模式中保存。我尝试对每个案例进行分组,以便稍后在 Match 对象中枚举每个案例。

有什么可以大有帮助的提示或修改吗?或者这是意料之中的缓慢运行?

为澄清而编辑:抱歉,我认为我第一次不够清楚。

我使用 xml 文件为特定布局生成正则表达式。然后我运行一个文件进行数据导入。我需要确保文件中的每一行都与它所说的模式匹配。因此,可以多次检查模式,可能是数千次。

【问题讨论】:

  • 您是否尝试将 CSV 与此正则表达式匹配?
  • 其制表符分隔来自 SAS 的输出
  • 你真的需要正则表达式来做到这一点吗?
  • 如果它会这么慢,那就不行!我试图确保数据类型与每行的指定布局相匹配。也可以有任意数量的布局。
  • 看看我下面的帖子。这应该可以让你用相当好的性能做你想做的事。

标签: c# .net xml regex performance


【解决方案1】:

默认情况下,单个表达式中可能有 50 个匹配组会有点慢。我会做一些事情来看看您是否可以确定性能挫折。

  1. 首先尝试硬编码与动态比较,看看是否有任何性能优势。
  2. 查看您的要求,看看是否有任何方法可以减少您需要评估的分组数量
  3. 如果需要,请使用分析器工具(例如 Ants Profiler)来查看减速的位置。

【讨论】:

    【解决方案2】:

    嗯。与连接它们相比,使用 StringBuilder 构建模式将节省一些周期。

    对此进行彻底的优化(可以明显衡量)很可能会通过其他方法来实现。

    正则表达式很慢......强大但很慢。解析文本文件,然后使用正则表达式进行比较以检索正确的数据位不会很快(取决于主机和文本文件的大小)。

    也许将数据存储在其他方法而不是大文本文件中会更有效地解析(也使用 XML?),或者可能是逗号分隔的列表。

    【讨论】:

    • 它实际上是用于数据导入。这就是我使用正则表达式的原因。确保导入的数据符合用户指定的给定格式。
    • 当你说它慢时,你的意思是有多慢?这些文件有多大?您要比较多少种模式?大概有一些相当常见的模式可以使用 if 和 substrings 来完成。如果不是将 RegEx 用于常见模式,而是自己将它们硬编码到应用程序中,它可能会更有效(尽管不太整洁)。顺便说一句,这个功能是否经常使用,如果是这样,必须有一种更有效的方式来存储这些数据,无论它直接去哪里(数据库?),然后在输入上执行验证。
    【解决方案3】:

    创建正则表达式的成本很高,如果编译它们,成本会更高。所以问题是您创建了许多正则表达式,但只使用了一次。

    您应该缓存它们以供重复使用,如果您不想经常使用它们,请不要编译它们。我从未测量过这一点,但我可以想象你将不得不使用一个简单的正则表达式超过 100 次才能超过编译成本。

    性能测试

    • 正则表达式:"^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+(?:[a-z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)$"

    • 输入:“www.stackoverflow.com”

    • 每次迭代的结果以毫秒为单位

      • 一个正则表达式,编译,10,000 次迭代:0.0018 毫秒
      • 一个正则表达式,未编译,10,000 次迭代:0.0021 毫秒
      • 每次迭代一个正则表达式,未编译,10,000 次迭代:0.0287 毫秒
      • 每次迭代一个正则表达式,编译,10,000 次迭代:4.8144 毫秒

    请注意,即使经过 10,000 次迭代,编译和未编译的正则表达式在比较它们的性能时仍然非常接近。随着迭代次数的增加,编译后的正则表达式性能更好。

    • 一个正则表达式,编译,1,000,000 次迭代:0.00137 毫秒
    • 一个正则表达式,未编译,1,000,000 次迭代:0.00225 毫秒

    【讨论】:

    • 也许我需要解释得更好一点。我不只使用一次。解析文件期间的任何时间都指向特定的布局。我检查该行是否与该布局的模式匹配。
    • 预编译,即使是单次使用,在我的测试中也会产生更好的正则表达式性能。单次使用并不多,但不会影响性能。
    • 因此,您为每个布局创建一个正则表达式,并在找到对应的行时使用该正则表达式,对吧?
    • 所以在我测试之后...编译正则表达式以供一次性使用肯定会降低性能 - 在我的测试中它慢了 100 倍以上。
    【解决方案4】:

    一些性能想法:

    • 使用[01] 而不是(0|1)
    • 使用非捕获组(?:expr) 而不是捕获组(如果您确实需要分组)

    编辑   您的值似乎是用空格分隔的,为什么不把它分开呢?

    【讨论】:

    • 是的,每个布局保留一个较小的正则表达式列表实际上可能更有益,并根据 '\t' 拆分字符串,然后逐个匹配。
    【解决方案5】:

    您正在使用正则表达式解析一个 50 列的 CSV 文件(使用制表符)?

    您应该只删除重复的选项卡,然后在 \t 上拆分文本。现在您将所有列都放在一个数组中。您可以使用 ColumnDef 对象集合来告诉您每列是什么。

    编辑:一旦你把事情分开,你可以选择使用正则表达式来验证每个值,这应该比使用巨大的单一正则表达式快得多。

    Edit2:您还可以获得额外的好处,即确切地知道哪些列格式错误,并且您可能会产生错误,例如“第 12 行第 30 列中的语法错误,预期日期格式。 "

    【讨论】:

    • 我开始认为这会更快更简单。
    • 这可能是迄今为止提出的最佳解决方案。我每天使用几十个复杂的正则表达式(处理发布文本和 XML)。 IME,一旦你的正则表达式达到一定的复杂性“临界质量”,性能就会下降。将这个问题分解成更小的块将是你绕过这个瓶颈的方法。
    【解决方案6】:

    我会手动构建一个词法分析器。

    在这种情况下,您看起来有一堆由制表符分隔的字段,记录由新行终止。 XML 文件似乎描述了列的顺序及其类型。

    编写代码来手动识别每个案例在最坏的情况下可能是 5-10 行代码。

    然后,您可能只想从 xml 文件中生成一组 PrimitiveType[] 值,然后调用下面的“GetValues”函数。

    这应该允许您通过输入流进行单次传递,这应该比使用正则表达式有很大的提升。

    您需要自己提供“ScanXYZ”方法。它们应该很容易编写。最好使用正则表达式来实现它们。

    public IEnumerable<object[]> GetValues(TextReader reader, PrimitiveType[] schema)
    {
       while (reader.Peek() > 0)
       {
           var values = new object[schema.Length];
           for (int i = 0; i < schema.Length; ++i)
           {
               switch (schema[i])
               {
                   case PrimitiveType.BIT:
                       values[i] = ScanBit(reader);
                       break;
                   case PrimitiveType.DATE:
                       values[i] = ScanDate(reader);
                       break;
                   case PrimitiveType.FLOAT:
                       values[i] = ScanFloat(reader);
                       break;
                   case PrimitiveType.INTEGER:
                       values[i] = ScanInt(reader);
                       break;
                   case PrimitiveType.STRING:
                       values[i] = ScanString(reader);
                       break;
               }
           }
    
           EatTabs(reader);
    
           if (reader.Peek() == '\n')
           {
                break;
           }
    
       if (reader.Peek() == '\n')
       {
           reader.Read();
       }
       else if (reader.Peek() >= 0)
       {
           throw new Exception("Extra junk detected!");
       }
    
       yield return values;
    
       }
    
       reader.Read();
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-21
      • 2014-05-04
      • 2021-11-25
      • 2012-11-05
      • 2012-08-19
      • 2018-11-26
      • 1970-01-01
      相关资源
      最近更新 更多