2017-08-24 15 views
6

Wpadłem na dziwny performance "artefakt" z String.StartsWith.Wydajność String.Starts przy użyciu StringComparison.OrdinalIgnoreCase

Wygląda na to, że String.StartsS przy użyciu OrdininalIgnoreCase jest szybszy niż użycie String.StartsWith bez określenia StringComparison. (2-4x szybciej)

Jednak sprawdzanie równości jest szybsze przy użyciu String.Equals bez StringComparison niż przy użyciu OrdininalIgnoreCase. (Chociaż wszystkie mają mniej więcej taką samą prędkość)

Pytanie brzmi: dlaczego? Dlaczego w obu przypadkach działają inaczej?

Oto kod używałem:

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

    } 

Odpowiedz

1

W przeciwieństwie do String.StartsWith (jak wskazuje Enigmativity), String.Equa ls nie używa domyślnie StringComparison, jeśli żadna nie jest określona. Zamiast tego używa własnej, niestandardowej implementacji, którą można zobaczyć pod poniższym linkiem: https://referencesource.microsoft.com/#mscorlib/system/string.cs,11648d2d83718c5e

Jest to nieco szybciej niż porównanie porządkowe.

Należy jednak pamiętać, że jeśli chcesz zachować spójność między porównaniami, użyj zarówno String.Equals, jak i String.StartsWith z ciągiemComparison lub nie działają one zgodnie z oczekiwaniami.

2

Wydaje się, że realizacja jest inna w 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"); 
     } 

Porównanie domyślny używany jest:

#if FEATURE_CORECLR 
           StringComparison.Ordinal); 
#else 
           StringComparison.CurrentCulture); 
#endif 
+0

Mogę zrozumieć, dlaczego byłoby szybciej z porządkowe - po prostu nie rozumiem, dlaczego String.Equals zachowuje się inaczej ... – MineR

+0

OK, więc patrząc na String.Equals, to faktycznie nie używa StringComparison, jeśli żaden nie jest określony - zamiast tego używa konkretnej implementacji. – MineR

Powiązane problemy