2016-07-28 23 views
1

Wir sind in der Verarbeitung der Integration von Kommunikation mit einer externen API. Es war bisher ein bisschen Kopfschmerzen wegen inkonsistenter Benennung, schlechter Dokumentation und unzuverlässiger Antwort-/Fehlermeldungen.Warum entfernen Regex und StringBuilder den Leerraum langsamer?

Eines der Dinge, mit denen wir es zu tun haben ist, dass bestimmte Anfragen, die wir an sie senden, die Länge der Strings begrenzen. Nichts Bahnbrechendes, aber jeder Request, der eine Zeichenfolge enthält, die die Längenanforderungen überschreitet, wird nur abgelehnt und schlägt fehl.

Unsere Lösung wurde eine Erweiterungsmethode für Zeichenfolge zu erstellen, die einen Teil dieser Länge beginnend bei Index 0.

Ich bin ein Junior-Entwickler in der maximalen Länge dauert nur und zurück, und dies ist mein erster Job Ich weiß, dass meine Lösungen möglicherweise nicht die elegantesten oder effizientesten sind. Wie auch immer, ich habe den Punkt angesprochen, dass wir mit unserer aktuellen Erweiterung relevante Informationen entfernen könnten, während potenziell wertloser Leerraum eingeschlossen wäre, da wir weder schneiden noch irgendetwas tun würden, um nach doppelten Stellen zu suchen Überladung der Erweiterung, die es Ihnen ermöglichen würde, auch Leerraum zu entfernen.

Ich habe 3 Lösungen gefunden, die alle doppelten Leerzeichen entfernen. Ich bin mir bewusst, dass die Regex-Methode die einzige ist, die wirklich den ganzen Leerraum entfernt, während die anderen beiden das Auftreten von zwei Leerzeichen hintereinander entfernen. Allerdings wird diese Seite ausschließlich in den USA verwendet, daher bin ich mir nicht sicher, ob die zusätzliche Zeit der Regex gerechtfertigt ist.

Mein Hauptinteresse bei der Veröffentlichung dieser Frage ist, dass jemand erklären kann, warum meine Methode mit StringBuilder im Vergleich zu den anderen beiden so ineffizient ist, es ist sogar langsamer als Regex, ich habe erwartet, dass es die schnellste der drei ist. Jeder Einblick wird hier geschätzt, ebenso wie ein Hinweis darauf, was ein besserer Weg ist als all das, was ich mir ausgedacht habe.

Hier sind meine drei Erweiterungen:

public static string SafeSubstringSomehowTheQuickest(this string stringToShorten, int maxLength) 
    { 
     if (stringToShorten?.Length < maxLength || string.IsNullOrWhiteSpace(stringToShorten)) return stringToShorten; 

     stringToShorten = stringToShorten.Trim(); 
     int stringOriginalLength = stringToShorten.Length; 
     int extraWhitespaceCount = 0; 
     for (int i = 0; i < stringOriginalLength - extraWhitespaceCount; i++) 
     { 
      int stringLengthBeforeReplace = stringToShorten.Length; 
      stringToShorten = stringToShorten.Replace(" ", " "); 
      if(stringLengthBeforeReplace < stringToShorten.Length) { extraWhitespaceCount += stringToShorten.Length - stringLengthBeforeReplace; } 
     } 

     return stringToShorten.Length > maxLength ? stringToShorten.Substring(0, maxLength) : stringToShorten; 
    } 

    public static string SafeSubstringWithRegex(this string stringToShorten, int maxLength) 
    { 
     if (stringToShorten?.Length < maxLength || string.IsNullOrWhiteSpace(stringToShorten)) return stringToShorten; 
     stringToShorten = System.Text.RegularExpressions.Regex.Replace(stringToShorten, @"\s{2,}", " ").Trim(); 

     return stringToShorten.Length > maxLength ? stringToShorten.Substring(0, maxLength) : stringToShorten; 
    } 

    public static string SafeSubstringFromBuilder(this string stringToShorten, int maxLength) 
    { 
     if (stringToShorten?.Length < maxLength || string.IsNullOrWhiteSpace(stringToShorten)) return stringToShorten; 

     StringBuilder bob = new StringBuilder(); 
     bool lastCharWasWhitespace = false; 

     foreach (char c in stringToShorten) 
     { 
      if (c == ' ' && !lastCharWasWhitespace) { bob.Append(c); } 
      lastCharWasWhitespace = c == ' '; 
      if (!lastCharWasWhitespace) { bob.Append(c); } 
     } 
     stringToShorten = bob.ToString().Trim(); 

     return stringToShorten.Length < maxLength ? stringToShorten : stringToShorten.Substring(0, maxLength); 
    } 

Hier ist mein schneller Test verwende ich Zeit vergleichen es für jede Erweiterung nimmt auszuführen:

static void Main(string[] args) 
    { 
     var stopwatch = new System.Diagnostics.Stopwatch(); 

     string test = 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      " + 
      " foo bar foobar   f oo  bar foobar  foofoo           " + 
      "barbar foo b ar                      "; 

     int stringStartingLength = test.Length; 
     int stringMaxLength = 30; 

     stopwatch.Start(); 
     string somehowTheQuickestResult = test.SafeSubstringSomehowTheQuickest(stringMaxLength); 
     stopwatch.Stop(); 
     var somehowTheQuickestResultTicks = stopwatch.ElapsedTicks; 

     stopwatch.Start(); 
     string regexResult = test.SafeSubstringWithRegex(stringMaxLength); 
     stopwatch.Stop(); 
     var regexResultTicks = stopwatch.ElapsedTicks; 

     stopwatch.Start(); 
     string stringBuilderResult = test.SafeSubstringFromBuilder(stringMaxLength); 
     stopwatch.Stop(); 
     var stringBuilderResultTicks = stopwatch.ElapsedTicks; 
    } 

das sind schließlich die Ergebnisse Die Ticks variieren bei jedem Durchlauf ein wenig, aber der Unterschied zwischen den drei Methoden ist ziemlich konsistent:

Alle drei geben die gleiche Zeichenfolge zurück von: "foo bar foobar f oo bar foobar"

somehowTheQuickestResult (Methode 1): 12840 ticks

regexResult (Methode 2): 14889 ticks

stringBuilderResult (Methode 3): 15798 ticks

+0

Es wäre wahrscheinlich noch schneller, wenn Sie mit einem Zeichenarray arbeiten würden, und die nicht weißen Leerzeichen über .. für größere String Vergleiche würden Sie wahrscheinlich sehr unterschiedliche Ergebnisse mit Ihren aktuellen Methoden – BugFinder

+0

Als Randnotiz, alle Methoden werden eine Reihe von vielen Leerzeichen unverändert zurückgeben, obwohl es zu lang wäre. – GSerg

+1

Für die Regex möchten Sie wahrscheinlich die Kompilierungszeit ausschließen, indem Sie eine explizite "static readonly" -Instanz mit erforderlichen [Flags] erstellen (https://msdn.microsoft.com/en-us/library/h5845fdz (v = vs .110) .aspx). Für den Zeichenfolgengenerator möchten Sie wahrscheinlich 'maxLength' als ['capacity'] übergeben (https://msdn.microsoft.com/en-us/library/h1h0a5sy (v = vs.110) .aspx). – GSerg

Antwort

5

Sie tun Ihr Benchmarking ein bisschen falsch.

Zuerst müssen Sie "Dinge aufwärmen" und JIT seine Arbeit machen lassen. Rufen Sie einfach Ihre drei Methoden an und verwerfen Sie die Ergebnisse.

Als nächstes ist der einzelne Versuch nicht repräsentativ. Probieren Sie den Durchschnitt (oder die Medianzeit) über 100 oder mehr Iterationen aus.

Drittens ist Ihre Verwendung von Stopwatch falsch. Start() nach Stop() setzt Intervallmessungen fort. Restart() ist der Weg zu gehen. Damit zeigen meine Tests folgende Ergebnisse:

9569 
314 
58 

So StringBuilder Weg ist eigentlich die schnellste.

+0

Großer Fang! Entschuldigung, das ist das erste Mal, dass ich die Stoppuhr benutzt habe. Ich werde daran arbeiten, dies zu wiederholen und einen besseren Durchschnitt über mehrere tausend Zyklen zu erhalten. – WRP