String.StartsWith 使用 StringComparison.OrdinalIgnoreCase 的表现

Performance of String.StartsWith using StringComparison.OrdinalIgnoreCase

我 运行 进入 st运行ge 性能 "artifact" 与 String.StartsWith。

似乎 String.StartsWith 使用 OrdinalIgnoreCase 比使用 String.StartsWith 而不指定 StringComparison 更快。 (快 2-4 倍)

但是,与使用 OrdinalIgnoreCase 相比,使用没有 StringComparison 的 String.Equals 检查相等性更快。 (虽然速度都差不多)

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

这是我使用的代码:

    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);

    }

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

因此,与 String.StartsWith(如 Enigmativity 所指出的)不同,如果指定了 none,String.Equals 默认情况下不使用任何 StringComparison。相反,它使用自己的自定义实现,您可以在下面的 link 中看到: https://referencesource.microsoft.com/#mscorlib/system/string.cs,11648d2d83718c5e

这比序数比较稍快。

但需要注意的是,如果您想要比较之间的一致性,请将 String.Equals 和 String.StartsWith 与 StringComparison 一起使用,否则它们不会按您预期的方式运行。