动态构建的正则表达式运行速度极慢!

时间:2009-04-29 19:41:19

标签: c# .net xml regex performance

我通过运行一些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文件为特定布局生成正则表达式。然后我运行一个文件进行数据导入。我需要确保文件中的每一行都匹配它应该是的模式。因此,可以多次检查模式,可能需要数千次。

6 个答案:

答案 0 :(得分:8)

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

您应该删除重复的标签,然后将文本拆分为\ t。现在,您将所有列都放在一个数组中。您可以使用ColumnDef对象集合来告诉您每列的内容。

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

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

答案 1 :(得分:5)

一些表现想法:

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

修改由于您的值似乎是由空格分隔的,为什么不将它拆分?

答案 2 :(得分:4)

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

你应该缓存它们以便重用,如果你不想经常使用它们,就不要编译它们。我从来没有对此表示过保证,但我可以想象你必须使用一个简单的正则表达式超过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 ms
    • 一个正则表达式,未编译,10,000次迭代: 0.0021 ms
    • 每次迭代一次正则表达式,未编译,10,000次迭代: 0.0287 ms
    • 每次迭代一次正则表达式,已编译,10,000次迭代: 4.8144 ms

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

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

答案 3 :(得分:2)

好。与连接它们相比,使用StringBuilder构建模式将节省几个周期。

对此极为激烈(可以明显测量)的优化最有可能通过其他方法实现。

正则表达式很慢......功能强大但速度慢。解析文本文件然后使用正则表达式进行比较只是为了检索正确的数据位不会非常快(取决于主机和文本文件的大小)。

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

答案 4 :(得分:1)

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

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

答案 5 :(得分:1)

我会手工制作一个词法分析器。

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

在最坏的情况下,编写用于手动识别每个案例的代码大概是5到10行代码。

然后,您只需要从xml文件生成PrimitiveType []值的arraay,然后调用下面的“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();
}