2012-09-11 8 views
32

ich den Quellcode des Open-Source-Projektes SignalR bin gerade, und ich sehe this diff code die „nicht String oder foreach in diesem heißen Code berechtigt ist, Verwenden Pfad“:„Verwenden String oder foreach nicht in diesem heißen Codepfad“

-   public static string MakeCursor(IEnumerable<Cursor> cursors) 
+   public static string MakeCursor(IList<Cursor> cursors) 
      { 
-    var sb = new StringBuilder(); 
-    bool first = true; 
-    foreach (var c in cursors) 
+    var result = ""; 
+    for (int i = 0; i < cursors.Count; i++) 
       { 
-     if (!first) 
+     if (i > 0) 
        { 
-      sb.Append('|'); 
+      result += '|'; 
        } 
-     sb.Append(Escape(c.Key)); 
-     sb.Append(','); 
-     sb.Append(c.Id); 
-     first = false; 
+     result += Escape(cursors[i].Key); 
+     result += ','; 
+     result += cursors[i].Id; 
       } 
-    return sb.ToString(); 
+    return result; 
      } 

ich verstehe, warum foreach manchmal weniger effizient sein könnte, und warum ist es durch für ersetzt.

Allerdings habe ich gelernt und erlebt, dass der StringBuilder der effizienteste Weg zum Verketten von Strings ist. Ich frage mich also, warum der Autor sich dafür entschieden hat, es durch eine Standardverkettung zu ersetzen.

Was ist hier und im Allgemeinen falsch bei der Verwendung von StringBuilder?

+8

Was denken Sie macht, dass 'foreach' ist weniger effizient als' for'? Es hängt von der Implementierung ab.Und ja, die Verwendung von wiederholten String-Verkettungen scheint hier eine schreckliche Idee zu sein - besonders wenn es viele Cursor gibt. –

+1

Stimme stark mit Jon überein. Ich wäre überrascht, wenn die StringBuilder-Version schlechter als die Verkettungsversion wäre. Abhängig von der Anzahl der Schleifeniterationen kann es helfen, einige "versteckte" implizite Objektinitialisierungen zu verhindern, wenn der StringBuilder mit einer größeren Startkapazität initialisiert wird. Haben Sie beide Versionen gemessen/profiliert, um sie zu vergleichen? –

+4

Sie sollten es [@dfowler] (http://stackoverflow.com/users/45091/dfowler) fragen :) –

Antwort

30

Ich machte den Code ändern und ja es machte einen großen Unterschied in der Anzahl der Zuordnungen (GetEnumerator()) Anrufe vs nicht. Stellen Sie sich vor, dieser Code ist Millionen Mal pro Sekunde. Die Anzahl der zugeordneten Enumeratoren ist lächerlich und kann vermieden werden.

edit: Wir haben jetzt Kontrolle, um invertieren alle Zuweisungen zu vermeiden (zum Schriftsteller zu schreiben direkt): https://github.com/SignalR/SignalR/blob/2.0.2/src/Microsoft.AspNet.SignalR.Core/Messaging/Cursor.cs#L36

+0

David, was ist nicht foreach tun, aber trotzdem StringBuilder benutzen? –

+2

Folge hier https://gist.github.com/3703926 – davidfowl

+0

Vielen Dank, dass Sie sich die Zeit genommen haben, einen Prototyp zu bauen, der uns den Beweis für die Vorzüge dieser Änderungen liefert. Wenn ich das richtig verstehe, ist ein StringBuilder besser geeignet, lange Strings zu behandeln, als den kurzen hintereinander in einer langen Schleife zu behandeln. – Larry

1

Wie viele Verkettungen machst du? Wenn mehrere, verwenden Sie StringBuilder. Wenn nur ein paar, dann wird der Overhead des Erstellens des StringBuilders jeden Vorteil überwiegen.

+0

Hinweis In diesem Fall müssen wir nur zu zwei Cursorn kommen, um mit 7 String-Verkettungen zu enden, was ziemlich unangenehm ist. –

+0

Nicht unbedingt - mit einer unbekannten Größe können Sie mehrere Neuzuweisungen des internen Puffers erhalten. Das kann teuer sein. – Oded

+0

Wäre dann nicht der richtige Ansatz, die Anzahl der Cursor zu betrachten und einen Pfad (String) für sehr wenige und den anderen für viele (StringBuilder) zu erstellen? –

3

Ich hoffe nur, dass der Typ, der das geändert hat, tatsächlich den Unterschied gemessen hat.

  • Es gibt Overhead bei der Instanziierung eines neuen Stringbuilders jedes Mal. Dies setzt auch die Speicher-/Speicherbereinigung unter Druck.
  • Der Compiler kann langsamer
  • Die FOR sein könnte in der Tat ‚stringbuilderlike‘ Code für einfache Verkettungen erzeugen, weil es könnte die Überprüfung der Grenzen erfordern, die mit foreach-Schleifen erfolgt nicht als die Compiler ‚weiß‘, sie sind in Grenzen.
+2

Das Projekt kennen und seine Fortschritte auf JabbR mit David Fowler verfolgen Ich bin fast überzeugt, dass genau dies den Grund für die Veränderung war. Er verbrachte viel Zeit damit, mit CLRProfiler und ähnlichem zu messen und den ganzen Müll anzugehen, der erstellt wurde. Hoffentlich wird er den Thread mit den Details abwiegen. –

1

werde ich mein Geld setzen auf

  StringBuilder sb = new StringBuilder(); 
      bool first = true; 
      foreach (Cursor c in cursors) 
      { 
       if (first) 
       { 
        first = false; // only assign it once 
       } 
       else 
       { 
        sb.Append('|'); 
       } 
       sb.Append(Escape(c.Key) + ',' + c.Id); 
      } 
      return sb.ToString(); 

Aber ich würde mein Geld und das Update von dfowler setzen. Schau dir den Link in seiner Antwort an.

2

Es hängt von der Anzahl der für die Funktion bereitgestellten Cursor s ab.

Die meisten Vergleiche zwischen den beiden Einstellungen scheinen StringBuilder gegenüber der Zeichenkombination zu favorisieren, wenn 4-10 Zeichenketten verwendet werden. Ich würde höchstwahrscheinlich bevorzugen StringBuilder, wenn ich keine expliziten Gründe nicht auch hatte (z. B. Leistungsvergleich der beiden Ansätze für mein Problem/Anwendung). Ich würde in Betracht ziehen, einen Puffer in der StringBuilder vorzubestellen, um (viele) Neuzuweisungen zu vermeiden.

Weitere Informationen zu diesem Thema finden Sie unter String concatenation vs String Builder. Performance und Concatenating with StringBuilders vs. Strings.

+1

Danke für die Informationen. Diese verwandte SO-Frage ist auch sehr interessant: http://stackoverflow.com/questions/550702/at-what-point-does-using-a-stringbuilder-become-ignificant-o--an-overhead – Larry