使用StringComparison.OrdinalIgnoreCase进行比较的字符串.StartsWith方法的性能

6

我在使用String.StartsWith时遇到了一个奇怪的性能问题。

似乎指定StringComparison参数为OrdinalIgnoreCase时,String.StartsWith的速度比不指定StringComparison的情况要快(快2-4倍)。

然而,在检查相等性方面,使用没有指定StringComparison参数的String.Equals速度比使用OrdinalIgnoreCase要快。(虽然它们的速度都差不多)

问题是为什么?为什么这两种情况下的表现不同?

这里是我使用的代码:

    public static void Test()
    {
        var options = new[] { "asd/klfe", "qer/jlkfe", "p33/ji", "fkjlfe", "asd/23", "bleash", "quazim", "ujv/3", "jvd/kfl" };
        Random r;

        const int trialSize = 100000;
        const int trials = 1000;
        Stopwatch swEqOp = new Stopwatch();
        Stopwatch swEq = new Stopwatch();
        Stopwatch swEqOrdinal = new Stopwatch();
        Stopwatch swStartsWith = new Stopwatch();
        Stopwatch swStartsWithOrdinal = new Stopwatch();
        for (int i = 0; i < trials; i++)
        {
            {
                r = new Random(1);
                swEqOp.Start();
                for (int j = 0; j < trialSize; j++)
                {
                    bool result = options[r.Next(options.Length)] == "asd/klfe";
                }
                swEqOp.Stop();
            }

            {
                r = new Random(1);
                swEq.Start();
                for (int j = 0; j < trialSize; j++)
                {
                    bool result = string.Equals(options[r.Next(options.Length)], "asd/klfe");
                }
                swEq.Stop();
            }

            {
                r = new Random(1);
                swEqOrdinal.Start();
                for (int j = 0; j < trialSize; j++)
                {
                    bool result = string.Equals(options[r.Next(options.Length)], "asd/klfe", StringComparison.OrdinalIgnoreCase);
                }
                swEqOrdinal.Stop();
            }

            {
                r = new Random(1);
                swStartsWith.Start();
                for (int j = 0; j < trialSize; j++)
                {
                    bool result = options[r.Next(options.Length)].StartsWith("asd/");
                }
                swStartsWith.Stop();
            }

            {
                r = new Random(1);
                swStartsWithOrdinal.Start();
                for (int j = 0; j < trialSize; j++)
                {
                    bool result = options[r.Next(options.Length)].StartsWith("asd/",StringComparison.OrdinalIgnoreCase);
                }
                swStartsWithOrdinal.Stop();
            }

        }

        //DEBUG with debugger attached. Release without debugger attached. AnyCPU both cases.

        //DEBUG : 1.54      RELEASE : 1.359
        Console.WriteLine("Equals Operator: " + swEqOp.ElapsedMilliseconds / 1000d);

        //DEBUG : 1.498      RELEASE : 1.349  <======= FASTEST EQUALS
        Console.WriteLine("String.Equals: " + swEq.ElapsedMilliseconds / 1000d);

        //DEBUG : 1.572      RELEASE : 1.405
        Console.WriteLine("String.Equals OrdinalIgnoreCase: " + swEqOrdinal.ElapsedMilliseconds / 1000d);

        //DEBUG : 14.234      RELEASE : 9.914
        Console.WriteLine("String.StartsWith: " + swStartsWith.ElapsedMilliseconds / 1000d);

        //DEBUG : 7.956      RELEASE : 3.953  <======= FASTEST StartsWith
        Console.WriteLine("String.StartsWith OrdinalIgnoreCase: " + swStartsWithOrdinal.ElapsedMilliseconds / 1000d);

    }
2个回答

2

看起来在public Boolean StartsWith(String value, StringComparison comparisonType)中的实现方式不同:

        switch (comparisonType) {
            case StringComparison.CurrentCulture:
                return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);

            case StringComparison.CurrentCultureIgnoreCase:
                return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase); 

            case StringComparison.InvariantCulture:
                return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None); 

            case StringComparison.InvariantCultureIgnoreCase:
                return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);

            case StringComparison.Ordinal:
                if( this.Length < value.Length) { 
                    return false; 
                }
                return (nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0); 

            case StringComparison.OrdinalIgnoreCase:
                if( this.Length < value.Length) {
                    return false; 
                }

                return (TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0); 

            default: 
                throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
        }

默认使用的比较方式是:
#if FEATURE_CORECLR
                              StringComparison.Ordinal);
#else
                              StringComparison.CurrentCulture); 
#endif

我能理解为什么使用Ordinal会更快 - 我只是不明白为什么String.Equals的行为会有所不同... - MineR
好的,经过查看String.Equals方法,如果没有指定StringComparison参数,它实际上不会使用StringComparison - 而是使用特定的实现。 - MineR

1
与Enigmativity所指出的String.StartsWith不同,如果未指定任何StringComparison,则String.Equals默认不使用任何StringComparison。相反,它使用自己的自定义实现,您可以在下面的链接中查看: https://referencesource.microsoft.com/#mscorlib/system/string.cs,11648d2d83718c5e 这比Ordinal Comparison略快。
但是需要注意的是,如果您希望比较保持一致,请同时使用String.Equals和String.StartsWith,并使用StringComparison,否则它们将无法按照您的预期运行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接