2017-08-24 3 views
6

J'ai couru dans un étrange "artefact" de performance avec String.StartsWith.Performance de String.StartsWith using StringComparison.OrdinalIgnoreCase

Il apparaît que String.StartsWith utilisant OrdinalIgnoreCase est plus rapide que l'utilisation de String.StartsWith sans spécifier de StringComparison. (2-4x plus rapide)

Cependant, vérifier l'égalité est plus rapide en utilisant String.Equals sans StringComparison qu'avec OrdinalIgnoreCase. (Bien que tous sont à peu près la même vitesse)

La question est pourquoi? Pourquoi fonctionnent-ils différemment dans les deux cas?

Voici le code que j'utilisais:

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

    } 

Répondre

1

Donc, contrairement à String.StartsWith (comme indiqué par Enigmativity), String.Equa ls n'utilise pas StringComparison par défaut si aucun n'est spécifié. Au lieu de cela, il utilise sa propre implémentation personnalisée, que vous pouvez voir sur le lien ci-dessous: https://referencesource.microsoft.com/#mscorlib/system/string.cs,11648d2d83718c5e

Ceci est légèrement plus rapide que la comparaison ordinale.

Mais il est important de noter que si vous voulez de la cohérence entre vos comparaisons, utilisez à la fois String.Equals et String.StartsWith avec une StringComparison, ou elles ne fonctionnent pas comme prévu.

2

Il semble que la mise en œuvre est différente dans 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"); 
     } 

La comparaison par défaut utilisé est:

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

Je pourrais comprendre pourquoi il serait plus rapide avec Ordinal - Je ne comprends pas pourquoi String.Equals se comporte différemment ... – MineR

+0

OK, donc en regardant String.Equals, il n'utilise pas réellement une StringComparison si none spécifié - à la place, il utilise une implémentation spécifique. – MineR