String.IndexOf(char)真的比手动搜索慢吗?

时间:2009-01-03 15:05:44

标签: .net performance string

在进行简单的性能测量时,我惊讶地发现调用String.IndexOf(char)实际上比手动调用慢!这是真的吗?!这是我的测试代码:

const string str = @"91023m lkajsdfl;jkasdf;piou-09324\\adf \asdf\45\ 65u\ 86\ 8\\\;";
static int testIndexOf() { return str.IndexOf('\\'); }
static int testManualIndexOf() {
    string s = str;
    for (int i = 0; i < s.Length; ++i)
        if (s[i] == '\\') return i;
    return -1;
}

我运行了每种方法2500万次,并使用Stopwatch测量时间。手动版本的速度始终比另一个快25%。

有什么想法吗?!


编辑2:使用.NET 4.0在另一台计算机上运行测试产生的结果与Marc Gravell的答案非常相似。 String.IndexOf(char) 比手动搜索更快


编辑今天(2009年1月4日)我想以更全面的方式检查此问题,因此我创建了一个新项目,并编写了您将在下面找到的代码。当我从cmd运行发布模式进行1亿次迭代时,我得到了以下结果:

  -   String.      IndexOf : 00:00:07.6490042
  - MyString.PublicIndexOf : 00:00:05.6676471
  - MyString.InternIndexOf : 00:00:06.1191796
  - MyString.PublicIndexOf2: 00:00:09.1363687
  - MyString.InternIndexOf2: 00:00:09.1182569

我运行这些测试至少20次,每次都得到几乎相同的结果。我的机器是XP SP3,VS 2008 SP1,P4 3.0 GHz,没有超线程和1 GB RAM。我发现结果真的很奇怪。如您所见,String.IndexOfPublicIndexOf慢约33%。更奇怪的是,我写了与internal相同的方法,它比public慢了约8%!我不明白发生了什么,我希望你能帮助我理解!

测试代码如下。 (对不起重复的代码,但我发现使用代理显示了不同的时间,publicinternal方法占用的时间相同。)

public static class MyString {
    public static int PublicIndexOf(string str, char value) {
        for (int i = 0; i < str.Length; ++i)
            if (str[i] == value) return i;
        return -1;
    }

    internal static int InternIndexOf(string str, char value) {
        for (int i = 0; i < str.Length; ++i)
            if (str[i] == value) return i;
        return -1;
    }

    public static int PublicIndexOf2(string str, char value, int startIndex) {
        if (startIndex < 0 || startIndex >= str.Length)
            throw new ArgumentOutOfRangeException("startIndex");
        for (; startIndex < str.Length; ++startIndex)
            if (str[startIndex] == value) return startIndex;
        return -1;
    }

    internal static int InternIndexOf2(string str, char value, int startIndex) {
        if (startIndex < 0 || startIndex >= str.Length)
            throw new ArgumentOutOfRangeException("startIndex");
        for (; startIndex < str.Length; ++startIndex)
            if (str[startIndex] == value) return startIndex;
        return -1;
    }
}

class Program {
    static void Main(string[] args) {
        int iterations = 100 * 1000 * 1000; // 100 millions
        char separator = '\\';
        string str = @"91023m lkajsdfl;jkasdf;piou-09324\\adf \asdf\45\ 65u\ 86\ 8\\\;";
        Stopwatch watch = new Stopwatch();

        // test String.IndexOf
        int sum = 0;
        watch.Start();
        for (int i = 0; i < iterations; ++i)
            sum += str.IndexOf(separator);
        watch.Stop();
        Console.WriteLine("  String.      IndexOf : ({0}, {1})", watch.Elapsed, sum);

        // test MyString.PublicIndexOf
        sum = 0;
        watch.Reset(); watch.Start();
        for (int i = 0; i < iterations; ++i)
            sum += MyString.PublicIndexOf(str, separator);
        watch.Stop();
        Console.WriteLine("MyString.PublicIndexOf : ({0}, {1})", watch.Elapsed, sum);

        // test MyString.InternIndexOf
        sum = 0;
        watch.Reset(); watch.Start();
        for (int i = 0; i < iterations; ++i)
            sum += MyString.InternIndexOf(str, separator);
        watch.Stop();
        Console.WriteLine("MyString.InternIndexOf : ({0}, {1})", watch.Elapsed, sum);

        // test MyString.PublicIndexOf2
        sum = 0;
        watch.Reset(); watch.Start();
        for (int i = 0; i < iterations; ++i)
            sum += MyString.PublicIndexOf2(str, separator,0);
        watch.Stop();
        Console.WriteLine("MyString.PublicIndexOf2: ({0}, {1})", watch.Elapsed, sum);

        // test MyString.InternIndexOf2
        sum = 0;
        watch.Reset(); watch.Start();
        for (int i = 0; i < iterations; ++i)
            sum += MyString.InternIndexOf2(str, separator,0);
        watch.Stop();
        Console.WriteLine("MyString.InternIndexOf2: ({0}, {1})", watch.Elapsed, sum);
    }
}

10 个答案:

答案 0 :(得分:8)

即使以“手动”方式更快地完成它也不是明智之举。今天,在您的.net版本中,您的CLR设置可能更快。也许。至少对于那个特定的字符串。但是你可以非常肯定内置的内容随着时间的推移会有更好的机会。用你的语言。而且IndexOf也更加清晰。

答案 1 :(得分:6)

也许这25%是调用功能的成本。

您的方法(testManualIndexOf)不会调用任何函数。

答案 2 :(得分:6)

您的测试实际上并不公平,因为您没有实现相同的功能:

  1. 您没有将任何参数传递给您的手动测试,而是使用const引用,与真实IndexOf方法中传递参数相比,这需要执行更少的操作码。
  2. 您的IndexOf测试实际上只进行了两次方法调用而不是一次。很可能外部方法调用将在Release版本中内联,而不是在调试器下运行,但是您的测试并不表示运行它的条件(如果是这种情况,那么您可以忽略这一点)。
  3. 但基本上,鉴于你的方法做得少,我对它运行得更快并不感到惊讶。

答案 3 :(得分:6)

(更新)

使用新发布的装备,我会得到以下数字,这符合我的预期:

String.      IndexOf : (00:00:06.3134669, -994967296)
MyString.PublicIndexOf : (00:00:07.0769368, -994967296)
MyString.InternIndexOf : (00:00:08.3463652, -994967296)
MyString.PublicIndexOf2: (00:00:12.0049268, -994967296)
MyString.InternIndexOf2: (00:00:12.4344756, -994967296)

(原)

我无法在控制台的版本中重现您的号码。我做了25M迭代,结果是:

  • 1556ms,(825000000)(testIndexOf)
  • 1798ms,(825000000)(testManualIndexOf)

所以IndexOf更快。我怀疑你的测试装备没有在命令行中以发布模式运行(你无法在连接调试器的情况下运行性能测试;即使IDE增加了太多的开销)。

这是我的装备:

static void Main()
{
    const int LOOP = 25000000;
    int chk1 = 0, chk2 = 0;
    Stopwatch watch1 = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++)
    {
        chk1 += testIndexOf();
    }
    watch1.Stop();
    Stopwatch watch2 = Stopwatch.StartNew();
    for (int i = 0; i < LOOP; i++)
    {
        chk2 += testManualIndexOf();
    }
    watch2.Stop();

    Console.WriteLine("{0}ms, ({1})", watch1.ElapsedMilliseconds, chk1);
    Console.WriteLine("{0}ms, ({1})", watch2.ElapsedMilliseconds, chk2);

}

答案 4 :(得分:5)

您的测试或您的环境存在明确的错误。

我只是运行此代码:

    static void Main(string[] args)
    {
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 25000000; i++)
                testIndexOf();
            sw.Stop();
            Console.WriteLine(sw.Elapsed); //2.27 s
        }
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();
            for (int i = 0; i < 25000000; i++)
                testManualIndexOf();
            sw.Stop();
            Console.WriteLine(sw.Elapsed); //9.81 s
        }

        Console.ReadLine();
    }

在两台不同的机器上,一台Vista,一台Linux / mono,发现内部IndexOf的速度提高了4倍。

答案 5 :(得分:2)

是不是可以下载.net框架的代码并看看str.IndexOf真正做了什么?

答案 6 :(得分:2)

此特定搜索的时间实际上是关键的,还是您正在进行致命的“无理由优化”?你用较短和较长的琴弦试了吗?有不同的搜索字符串?内置函数可以针对其他情况进行优化,在这种情况下,搜索可能会变慢并且您的特定情况最终会变慢。在我对更多案例进行更多测试之前,我不会谴责IndexOf API“总是更慢”。

答案 7 :(得分:2)

嗯,你在第一个实例中有额外的函数调用开销。您是否尝试在方法中包装手动搜索并从测试中调用它 - 这确实是最佳比较,因为每次您想要在字符串中搜索字符时,您不太可能想要编写该循环。

答案 8 :(得分:1)

如前所述,我认为函数调用可能会解释一些差异。参数传递和检查需要时间。

我试着用Reflector看到IndexOf的代码,但是......运气不好,这是一个内部CLR调用。我认为仅仅通过手动方法就可以选择它,因为它可以通过CLR改进来改进。

答案 9 :(得分:1)

我将您的代码转换为实际方法,并添加了一个测试来检查null。然后我编写了一个循环来生成1000万随机长度的随机字符串(1 <= length <= 250),并使用实例方法String.IndexOf和自定义testManualIndexOf比较时间。 String.IndexOf花费了5159毫秒,testManualIndexOf花费了4838毫秒,相差6.2%。以下是不那么漂亮的代码:

public static void Main(string[] args) {
    int nTrials = 10000000;
    Random rg = new Random(1);
    Stopwatch sw = new Stopwatch();
    for (int i = 0; i < nTrials; i++) {
        int len = 1 + rg.Next(250);
        char[] randomChar = new char[len];
        for(int j = 0; j < len; j++) {
            randomChar[j] = GetRandomLetterOrDigit(rg);
        }
        string s = new string(randomChar);
        char findChar = GetRandomLetterOrDigit(rg);
        sw.Start();
        int index = s.IndexOf(findChar);
        //int index = testManualIndexOf(s, findChar);
        sw.Stop();
    }
    Console.WriteLine(sw.ElapsedMilliseconds);
}

private static char GetRandomLetterOrDigit(Random rg) {
    char c;
    int rc = rg.Next(62);

    if (rc < 26) {
        c = (char)('A' + rc);
    }
    else if (rc < 52) {
        c = (char)('a' + rc - 26);
    }
    else {
        c = (char)('0' + rc - 52);
    }
    return c;
}

private static int testManualIndexOf(string s, char c) {
    if (s == null) {
        throw new ArgumentNullException("s");
    }
    for (int i = 0; i < s.Length; ++i) {
        if (s[i] == c) return i;
    }
    return -1;
}