【问题标题】:How to outperform this regex replacement?如何超越这个正则表达式替换?
【发布时间】:2011-02-12 19:12:08
【问题描述】:

经过大量测量,我在我们的一个 Windows 服务中确定了一个我想要优化的热点。我们正在处理可能有多个连续空格的字符串,我们希望减少到只有一个空格。我们为此任务使用静态编译的正则表达式:

private static readonly Regex 
    regex_select_all_multiple_whitespace_chars = 
        new Regex(@"\s+",RegexOptions.Compiled);

然后按如下方式使用:

var cleanString=
    regex_select_all_multiple_whitespace_chars.Replace(dirtyString.Trim(), " ");

这条线被调用了几百万次,并且被证明是相当密集的。我试图写一些更好的东西,但我很难过。鉴于正则表达式的处理要求相当适中,肯定有更快的东西。 unsafe 使用指针处理可以进一步加快速度吗?

编辑:

感谢您对这个问题的精彩回答......最出乎意料!

【问题讨论】:

  • 您是经常在较小的字符串上运行它,还是在较大的字符串上运行它
  • @rob,它在大约 10-40 个字符长的字符串上运行

标签: c# regex optimization string


【解决方案1】:

这大约快三倍:

private static string RemoveDuplicateSpaces(string text) {
  StringBuilder b = new StringBuilder(text.Length);
  bool space = false;
  foreach (char c in text) {
    if (c == ' ') {
      if (!space) b.Append(c);
      space = true;
    } else {
      b.Append(c);
      space = false;
    }
  }
  return b.ToString();
}

【讨论】:

  • 我选择了这个变体。使用 for(int i... 代替 foreach,使用 char c=text[i] 可以获得更快的速度。再次感谢大家。
【解决方案2】:

这个怎么样...

public string RemoveMultiSpace(string test)
{
var words = test.Split(new char[] { ' ' }, 
    StringSplitOptions.RemoveEmptyEntries);
return string.Join(" ", words);
}

使用 NUnit 运行测试用例:
测试时间以毫秒为单位。

Regex Test time: 338,8885
RemoveMultiSpace Test time: 78,9335
private static readonly Regex regex_select_all_multiple_whitespace_chars =
   new Regex(@"\s+", RegexOptions.Compiled);

[Test]
public void Test()
{
    string startString = "A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      ";
    string cleanString;
    Trace.WriteLine("Regex Test start");
    int count = 10000;
    Stopwatch timer = new Stopwatch();
    timer.Start();
    for (int i = 0; i < count; i++)
    {
        cleanString = regex_select_all_multiple_whitespace_chars.Replace(startString, " ");
    }
    var elapsed = timer.Elapsed;
    Trace.WriteLine("Regex Test end");
    Trace.WriteLine("Regex Test time: " + elapsed.TotalMilliseconds);

    Trace.WriteLine("RemoveMultiSpace Test start");
    timer = new Stopwatch();
    timer.Start();
    for (int i = 0; i < count; i++)
    {
        cleanString = RemoveMultiSpace(startString);
    }
    elapsed = timer.Elapsed;
    Trace.WriteLine("RemoveMultiSpace Test end");
    Trace.WriteLine("RemoveMultiSpace Test time: " + elapsed.TotalMilliseconds);
}

public string RemoveMultiSpace(string test)
{
    var words = test.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
    return string.Join(" ", words);
}

编辑:
进行了更多测试并添加了 Guffa 的基于 StringBuilder 的方法“RemoveDuplicateSpaces”。
所以我的结论是StringBuilder方法在空格很多的时候比较快,但是空格比较少的话string split方法稍微快一点。

Cleaning file with about 30000 lines, 10 iterations
RegEx time elapsed: 608,0623
RemoveMultiSpace time elapsed: 239,2049
RemoveDuplicateSpaces time elapsed: 307,2044

Cleaning string, 10000 iterations:
A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      A B  C   D    E     F      
RegEx time elapsed: 590,3626
RemoveMultiSpace time elapsed: 159,4547
RemoveDuplicateSpaces time elapsed: 137,6816

Cleaning string, 10000 iterations:
A      B      C      D      E      F      A      B      C      D      E      F      A      B      C      D      E      F      A      B      C      D      E      F      A      B      C      D      E      F      A      B      C      D      E      F      A      B      C      D      E      F      A      B      C      D      E      F      
RegEx time elapsed: 290,5666
RemoveMultiSpace time elapsed: 64,6776
RemoveDuplicateSpaces time elapsed: 52,4732

【讨论】:

  • 这很好。一个快速的浅层测试表明这比 StringBuilder 方法更快。
  • 更新:抱歉,一开始我忘记了 RemoveMultiSpace 方法的循环。
  • 我认为测试结果看起来太神了。 ;) 它比使用 StringBuilder 快一点,但它也会创建一堆临时字符串。您必须使用一些真实数据来尝试它们,看看这对性能有何影响。
  • 有人知道为什么这比 StringBuilder 方法快吗?
  • @Heinzi:它在框架中使用优化(不安全模式或非托管)方法,比使用 StringBuilder 的托管代码更快。尽管 Split-Join 做了一些额外的工作,但在某些情况下它仍然更快。
【解决方案3】:

目前,您正在用另一个空格替换一个空格。尝试匹配\s{2,}(或类似的东西,如果你想替换单个换行符和其他字符)。

【讨论】:

  • 我同意。对于关键点,快速测试表明我们可以做得更好。
  • 可能是因为代码更改与性能的比率非常好。仅在正则表达式中更改几个字符即可提高 25%。 :)
【解决方案4】:

只有一个建议,如果您的数据没有 unicode 空格,而不是 \s+ 使用 [ \r\n]+[ \n]+ 或只是  +(如果只有空格),基本上将其限制为最小字符集.

【讨论】:

  • @" {2,}" 在我的数据上的表现几乎(在 10% 以内)与此处建议的最佳方法一样好,答案非常好!
【解决方案5】:

我很好奇直接的实现会如何执行:

    static string RemoveConsecutiveSpaces(string input)
    {
        bool whiteSpaceWritten = false;
        StringBuilder sbOutput = new StringBuilder(input.Length);

        foreach (Char c in input)
        {
            if (c == ' ')
            {
                if (!whiteSpaceWritten)
                {
                    whiteSpaceWritten = true;
                    sbOutput.Append(c);
                }
            }
            else
            {
                whiteSpaceWritten = false;
                sbOutput.Append(c);
            }
        }

        return sbOutput.ToString();
    }

【讨论】:

  • 它的速度大约是正则表达式的三倍。看我的答案(代码基本相同)。
【解决方案6】:

您不能使用正则表达式。例如:

private static string NormalizeWhitespace(string test)
{
    string trimmed = test.Trim();

    var sb = new StringBuilder(trimmed.Length);

    int i = 0;
    while (i < trimmed.Length)
    {
        if (trimmed[i] == ' ')
        {
            sb.Append(trimmed[i]);

            do { i++; } while (i < trimmed.Length && trimmed[i] == ' ');
        }

        sb.Append(trimmed[i]);

        i++;
    }

    return sb.ToString();
}

使用此方法和以下测试台:

private static readonly Regex MultipleWhitespaceRegex = new Regex(
    @"\s+", 
    RegexOptions.Compiled);

static void Main(string[] args)
{
    string test = "regex  select    all multiple     whitespace   chars";

    const int Iterations = 15000;

    var sw = new Stopwatch();

    sw.Start();
    for (int i = 0; i < Iterations; i++)
    {
        NormalizeWhitespace(test);
    }
    sw.Stop();
    Console.WriteLine("{0}ms", sw.ElapsedMilliseconds);

    sw.Reset();

    sw.Start();
    for (int i = 0; i < Iterations; i++)
    {
        MultipleWhitespaceRegex.Replace(test, " ");
    }
    sw.Stop();
    Console.WriteLine("{0}ms", sw.ElapsedMilliseconds);
}

我得到了以下结果:

// NormalizeWhitespace - 27ms
// Regex - 132ms

请注意,这仅通过一个非常简单的示例进行了测试,可以通过删除对 String.Trim 的调用来进一步优化,并且只是为了说明正则表达式有时不是最佳答案。

【讨论】:

  • 除了空格不仅仅是' ',而且你不使用变量trimemd,这正是我要建议的。 +1
  • @Rob Fonseca-Ensor,感谢您发现那个,这是最后一分钟的不完整更改。 :)
【解决方案7】:

因为它是一个如此简单的表达式,用一个空格替换两个或多个空格,去掉 Regex 对象并自己硬编码替换(在 C++/CLI 中):

String ^text = "Some   text  to process";
bool spaces = false;
// make the following static and just clear it rather than reallocating it every time
System::Text::StringBuilder ^output = gcnew System::Text::StringBuilder;
for (int i = 0, l = text->Length ; i < l ; ++i)
{
  if (spaces)
  {
    if (text [i] != ' ')
    {
      output->Append (text [i]);
      spaces = false;
    }
  }
  else
  {
    output->Append (text [i]);
    if (text [i] == ' ')
    {
      spaces = true;
    }
  }
}
text = output->ToString ();

【讨论】:

  • 嗯,不确定 C++/CLI 是否合理!
  • 语法略有不同,但将上面的内容更改为 C# 并不难。我刚刚打开了一个 C++/CLI 项目来测试代码(是的,我知道,但这是我必须使用的)。
【解决方案8】:

数组总是会更快

        public static string RemoveMultiSpace(string input)
    {
        var value = input;

        if (!string.IsNullOrEmpty(input))
        {
            var isSpace = false;
            var index = 0;
            var length = input.Length;
            var tempArray = new char[length];
            for (int i = 0; i < length; i++)
            {
                var symbol = input[i];
                if (symbol == ' ')
                {
                    if (!isSpace)
                    {
                        tempArray[index++] = symbol;
                    }
                    isSpace = true;
                }
                else
                {
                    tempArray[index++] = symbol;
                    isSpace = false;
                }
            }
            value = new string(tempArray, 0, index);
        }

        return value;
    }

【讨论】:

  • 过度使用 var 关键字。为什么要将数组复制到另一个数组而不是仅从 tempArray 创建字符串?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-11
  • 2022-01-22
  • 2012-02-12
  • 2015-02-21
  • 1970-01-01
相关资源
最近更新 更多