字符串.IndexOf性能

本文关键字:性能 IndexOf 字符串 | 更新日期: 2023-09-27 18:12:56

这段简单的c#代码旨在在HTML中查找脚本块,在一个74K的字符串上运行仅需0.5秒,其中只有9个脚本块。这是2.8Ghz i7 CPU上未调试的二进制版本。我对这段代码进行了几次运行,以确保性能不会受到JIT的影响。它不是。

这是VS2010 . net 4.0客户端配置文件。x64

为什么这么慢?

                int[] _exclStart = new int[100];
                int[] _exclStop = new int[100];
                int _excl = 0;
                for (int f = input.IndexOf("<script", 0); f != -1; )
                {
                    _exclStart[_excl] = f;
                    f = input.IndexOf("</script", f + 8);
                    if (f == -1)
                    {
                        _exclStop[_excl] = input.Length;
                        break;
                    }
                    _exclStop[_excl] = f;
                    f = input.IndexOf("<script", f + 8);
                    ++_excl;
                }

字符串.IndexOf性能

我以本页上的源代码为例,然后将内容复制8次,结果页面长约334,312字节。使用StringComparision。序数产生巨大的性能差异。

string newInput = string.Format("{0}{0}{0}{0}{0}{0}{0}{0}", input.Trim().ToLower());
//string newInput = input.Trim().ToLower();
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
int[] _exclStart = new int[100];
int[] _exclStop = new int[100];
int _excl = 0;
for (int f = newInput.IndexOf("<script", 0, StringComparison.Ordinal); f != -1; )
{
    _exclStart[_excl] = f;
    f = newInput.IndexOf("</script", f + 8, StringComparison.Ordinal);
    if (f == -1)
    {
        _exclStop[_excl] = newInput.Length;
        break;
    }
    _exclStop[_excl] = f;
    f = newInput.IndexOf("<script", f + 8, StringComparison.Ordinal);
    ++_excl;
}
sw.Stop();
Console.WriteLine(sw.Elapsed.TotalMilliseconds);

运行5次,每次都产生几乎相同的结果(循环时间没有明显变化,因此对于这个简单的代码,JIT几乎没有花费时间来编译它)

使用原始代码的输出(以毫秒为单位):

10.2786
11.4671
11.1066
10.6537
10.0723

使用上述代码代替输出(以毫秒为单位):

0.3055
0.2953
0.2972
0.3112
0.3347
请注意,我的测试结果大约是0.010秒(原始代码)和0.0003秒(序数代码)。这意味着除了这段代码之外,还有其他地方出了问题。

如果正如你所说,使用StringComparison.Ordinal对你的性能没有任何作用,那么这意味着你使用不正确的计时器来计时你的性能,或者你在读取input值时有很大的开销,例如从流中再次读取它,否则你不会意识到。

使用。net 4客户端配置文件在3GHz i5上运行的Windows 7 x64下测试。

建议:

  1. 使用StringComparison.Ordinal
  2. 确保您使用System.Diagnostics.Stopwatch来计时性能
  3. input声明一个局部变量,而不是使用函数外部的值(例如:string newInput = input.Trim().ToLower();)
我再次强调,使用您提供的完全相同的代码,我获得的速度比快50倍,而测试数据的大小显然大于4倍。这意味着我的测试运行一些比你的快200倍,这不是任何人都会期望的,因为我们都运行相同的环境,只有i5(我)和i7(你)。

您使用的IndexOf重载是文化敏感的,这将影响性能。相反,使用:

input.IndexOf("<script", 0, StringComparison.Ordinal); 

我建议使用RegEx,它提供了显著的性能改进,因为表达式只编译一次。然而IndexOf本质上是一个基于每个字符运行的循环,这可能意味着,你在主for循环中有3个"循环",当然,IndexOf不会像常规循环那么慢,但是当输入大小增加时,时间仍然会增加。Regex有内置的函数,可以返回你定义的每个模式出现的次数和位置。

编辑:这可能会更多地揭示IndexOf IndexOf Perf的性能

我刚刚在Windows 7上使用。net 4.0测试了IndexOf的性能

public void Test()
{
    var input = "Hello world, I'm ekk. This is test string";
    TestStringIndexOfPerformance(input, StringComparison.CurrentCulture);
    TestStringIndexOfPerformance(input, StringComparison.InvariantCulture);
    TestStringIndexOfPerformance(input, StringComparison.Ordinal);
    Console.ReadLine();
}
private static void TestStringIndexOfPerformance(string input, StringComparison stringComparison)
{
    var count = 0;
    var startTime = DateTime.UtcNow;
    TimeSpan result;
    for (var index = 0; index != 1000000; index++)
    {
        count = input.IndexOf("<script", 0, stringComparison);
    }
    result = DateTime.UtcNow.Subtract(startTime);
    Console.WriteLine("{0}: {1}", stringComparison, count);
    Console.WriteLine("Total time: {0}", result.TotalMilliseconds);
    Console.WriteLine("--------------------------------");
}

,结果是:

CurrentCulture:
    Total time: 225.4008
InvariantCulture:
    Total time: 187.2003
Ordinal:
    Total time: 124.8003

您可以看到Ordinal的性能稍好一些。

我在这里不讨论代码,这可能是用Regex等编写的…但是为了我是缓慢的,因为IndexOf() *里面的* for总是重新扫描字符串从一开始(它总是从索引0开始)尝试扫描从最后一个出现找到代替。