2010-08-13 10 views
52

Ich habe begonnen, StringBuilder anstelle von geraden Verkettung zu verwenden, aber es scheint, als ob es eine entscheidende Methode fehlt. So setzte ich es selbst, als Erweiterung:Unterdrücke ich die Effizienz von StringBuilder?

public void Append(this StringBuilder stringBuilder, params string[] args) 
{ 
    foreach (string arg in args) 
     stringBuilder.Append(arg); 
} 

Damit wird das folgende Chaos:

StringBuilder sb = new StringBuilder(); 
... 
sb.Append(SettingNode); 
sb.Append(KeyAttribute); 
sb.Append(setting.Name); 

In diesem:

sb.Append(SettingNode, KeyAttribute, setting.Name); 

ich sb.AppendFormat("{0}{1}{2}",... verwenden könnte, aber dies scheint auch weniger bevorzugt und noch schwerer zu lesen. Ist meine Erweiterung eine gute Methode, oder untergräbt sie irgendwie die Vorteile von StringBuilder? Ich versuche nicht, irgendetwas vorzeitig zu optimieren, da meine Methode mehr auf Lesbarkeit als auf Geschwindigkeit ausgerichtet ist, aber ich würde auch gerne wissen, dass ich mich nicht selbst in den Fuß schieße.

+7

Sollte darauf hinweisen, dass, wenn Sie bereits einen StringBuilder haben, würde gerade String Concat schneller sein. z.B. String s = "orig" + SettingNode + KeyAttribute + setting.Name; – hemp

+0

@Hemp: Nur leicht. Es wird den Aufruf von Append los. Alles andere sollte gleich sein. Und das setzt voraus, dass es keine anderen Anhänge gibt. –

+0

@Hemp: Was ist mit String s = String.Concat ("orig", SettingNode, KeyAttribute, setting.Name), ich denke, das wäre schneller als mit "+" für concat. –

Antwort

69

Ich sehe kein Problem mit Ihrer Erweiterung. Wenn es für dich funktioniert, ist alles gut.

ich mich prefere:

sb.Append(SettingNode) 
    .Append(KeyAttribute) 
    .Append(setting.Name); 
+24

+1 Mach es so! Wenn sie es geschrieben haben, um 'this' zurückzugeben, dann ist das genau das, was die DesignerInnen beabsichtigten. –

+1

fließend oder fließend – kenny

+2

@kenny Fragen Sie, wie dieser Stil heißt? AFAIK, es ist bekannt als "fließende Schnittstellen." –

3

Abgesehen von ein wenig Aufwand sehe ich persönlich keine Probleme damit. Definitiv besser lesbar. Solange Sie eine angemessene Anzahl von Params passieren, sehe ich das Problem nicht.

1

würde ich nicht sagen, dass Sie es Effizienz sind untergraben, aber Sie können zur Verfügung etwas ineffizient, wenn eine effizientere Methode ist tun. AppendFormat ist, was ich denke, dass Sie hier wollen. Wenn die {0} {1} {2} -Strange, die ständig verwendet wird, zu hässlich ist, tendiere ich dazu, meine Formatzeichenfolgen in die obigen Konstanten zu setzen, so dass das Aussehen mehr oder weniger mit Ihrer Erweiterung übereinstimmt.

sb.AppendFormat(SETTING_FORMAT, var1, var2, var3); 
+1

Ich weiß nicht, dass AppendFormat effizienter ist. Sicher, es ruft 'Append' einmal auf, aber unter der Haube kann es' StringBuildinger.Append (String.Format (...)) 'sein, das eine zusätzliche Zeichenkette instanziiert. Natürlich ist es Vermutung, bis jemand zerlegt und sieht, wie der IL tatsächlich aussieht. – STW

+0

@STW: Um sicher zu sein kann ich nicht sagen, als ob ich weiß, ich würde nur ein String denken. Format ist jedoch effizienter als drei sb. Anhängt .. Weiß nicht .. –

+0

Ich würde geneigt sein, mit AppendFormat zu gehen, besonders wenn die Zeichenkette dem Benutzer angezeigt wird; Wenn die Zeichenfolge neu formatiert werden muss (z. B. für die Lokalisierung), müssen Sie Ihren Code nicht ändern. –

0

Letztendlich kommt es darauf an, was zu weniger String-Erstellung führt. Ich habe das Gefühl, dass die Erweiterung zu einer höheren Anzahl von Strings führt, die das String-Format verwenden. Aber die Leistung wird wahrscheinlich nicht so anders sein.

9

Es ist ein bisschen Overhead, das zusätzliche Array zu erstellen, aber ich bezweifle, dass es viel ist. Sie sollten messen

Wenn stellt sich heraus, dass der Overhead des Erstellens von String-Arrays ist signifikant, können Sie es durch mehrere Überladungen - eine für zwei Parameter, eins für drei, eins für vier etc ... so dass es mildern Nur wenn Sie zu einer höheren Anzahl von Parametern kommen (zB sechs oder sieben), muss das Array erstellt werden. Die Überlastungen würde so aussehen:

public void Append(this builder, string item1, string item2) 
{ 
    builder.Append(item1); 
    builder.Append(item2); 
} 

public void Append(this builder, string item1, string item2, string item3) 
{ 
    builder.Append(item1); 
    builder.Append(item2); 
    builder.Append(item3); 
} 

public void Append(this builder, string item1, string item2, 
        string item3, string item4) 
{ 
    builder.Append(item1); 
    builder.Append(item2); 
    builder.Append(item3); 
    builder.Append(item4); 
} 

// etc 

Und dann noch eine letzte Überlastung mit params, z.B.

public void Append(this builder, string item1, string item2, 
        string item3, string item4, params string[] otherItems) 
{ 
    builder.Append(item1); 
    builder.Append(item2); 
    builder.Append(item3); 
    builder.Append(item4); 
    foreach (string item in otherItems) 
    { 
     builder.Append(item); 
    } 
} 

Ich würde auf jeden Fall erwarten, dass diese (oder einfach nur Ihre ursprüngliche Erweiterungsmethode) schneller als AppendFormat mit - was das Format Zeichenfolge analysieren muss, nachdem alle.

Bitte beachte, dass ich nicht jeder diese Überlastungen rufen andere pseudo-rekursiv gemacht hat - ich vermuten sie inlined werden würde, aber wenn sie nicht der Aufwand stieg um einen neuen Stapelrahmen usw. der Einrichtung könnte am Ende wird von Bedeutung. (Wir gehen davon aus, dass der Overhead des Arrays signifikant ist, wenn wir soweit sind.

) wie folgt
+0

Visual Studio scheint 'params string [] args' anstelle von' string arg0, string arg1' usw. zu verwenden. Muss ich 'string arg0, string arg1, ..., params string [] moreArgs' zu tun haben Soll das funktionieren? – dlras2

+0

@Jon Nichts für ungut, aber die mehrere Überladungen sieht abscheulich. Machen Sie zumindest einen Regressionsanruf für jede zusätzliche Überladung, und warum verwenden Sie nicht einfach params string [] args? –

+3

@Lucas: Der Sinn der Verwendung einiger Überladungen mit mehreren Parametern besteht darin, den Overhead des Arrays zu vermeiden, der jedes Mal erzeugt wird, wenn Sie eine Methode mit einem 'params' -Parameter verwenden. Ja, vielleicht ist es zuviel - aber das ist eine Frage der Performance, daher die Antwort. (Ich habe am Anfang gesagt, dass es nicht viel Overhead sein wird.) Wie für einen Anruf einen anderen - ich denke, dass würde nicht weh * wenn * es ist inline ... aber sonst ist es ein extra Stack-Frame jedes Mal. –

32

Fragen können jederzeit mit einem einfachen Testfall beantwortet werden.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 

namespace SBTest 
{ 
    class Program 
    { 
     private const int ITERATIONS = 1000000; 

     private static void Main(string[] args) 
     { 
      Test1(); 
      Test2(); 
      Test3(); 
     } 

     private static void Test1() 
     { 
      var sw = Stopwatch.StartNew(); 
      var sb = new StringBuilder(); 

      for (var i = 0; i < ITERATIONS; i++) 
      { 
       sb.Append("TEST" + i.ToString("00000"), 
          "TEST" + (i + 1).ToString("00000"), 
          "TEST" + (i + 2).ToString("00000")); 
      } 

      sw.Stop(); 
      Console.WriteLine("Testing Append() extension method..."); 
      Console.WriteLine("--------------------------------------------"); 
      Console.WriteLine("Test 1 iterations: {0:n0}", ITERATIONS); 
      Console.WriteLine("Test 1 milliseconds: {0:n0}", sw.ElapsedMilliseconds); 
      Console.WriteLine("Test 1 output length: {0:n0}", sb.Length); 
      Console.WriteLine(""); 
     } 

     private static void Test2() 
     { 
      var sw = Stopwatch.StartNew(); 
      var sb = new StringBuilder(); 

      for (var i = 0; i < ITERATIONS; i++) 
      { 
       sb.Append("TEST" + i.ToString("00000")); 
       sb.Append("TEST" + (i+1).ToString("00000")); 
       sb.Append("TEST" + (i+2).ToString("00000")); 
      } 

      sw.Stop();  
      Console.WriteLine("Testing multiple calls to Append() built-in method..."); 
      Console.WriteLine("--------------------------------------------"); 
      Console.WriteLine("Test 2 iterations: {0:n0}", ITERATIONS); 
      Console.WriteLine("Test 2 milliseconds: {0:n0}", sw.ElapsedMilliseconds); 
      Console.WriteLine("Test 2 output length: {0:n0}", sb.Length); 
      Console.WriteLine(""); 
     } 

     private static void Test3() 
     { 
      var sw = Stopwatch.StartNew(); 
      var sb = new StringBuilder(); 

      for (var i = 0; i < ITERATIONS; i++) 
      { 
       sb.AppendFormat("{0}{1}{2}", 
        "TEST" + i.ToString("00000"), 
        "TEST" + (i + 1).ToString("00000"), 
        "TEST" + (i + 2).ToString("00000")); 
      } 

      sw.Stop(); 
      Console.WriteLine("Testing AppendFormat() built-in method..."); 
      Console.WriteLine("--------------------------------------------");    
      Console.WriteLine("Test 3 iterations: {0:n0}", ITERATIONS); 
      Console.WriteLine("Test 3 milliseconds: {0:n0}", sw.ElapsedMilliseconds); 
      Console.WriteLine("Test 3 output length: {0:n0}", sb.Length); 
      Console.WriteLine(""); 
     } 
    } 

    public static class SBExtentions 
    { 
     public static void Append(this StringBuilder sb, params string[] args) 
     { 
      foreach (var arg in args) 
       sb.Append(arg); 
     } 
    } 
} 

Auf meinem PC, der Ausgang ist:

Testing Append() extension method... 
-------------------------------------------- 
Test 1 iterations: 1,000,000 
Test 1 milliseconds: 1,080 
Test 1 output length: 29,700,006 

Testing multiple calls to Append() built-in method... 
-------------------------------------------- 
Test 2 iterations: 1,000,000 
Test 2 milliseconds: 1,001 
Test 2 output length: 29,700,006 

Testing AppendFormat() built-in method... 
-------------------------------------------- 
Test 3 iterations: 1,000,000 
Test 3 milliseconds: 1,124 
Test 3 output length: 29,700,006 

So Ihre Erweiterungsmethode nur geringfügig langsamer als die Append() -Methode und ist etwas schneller als die AppendFormat() -Methode, aber in allen 3 Fälle, der Unterschied ist völlig zu trivial, um sich zu sorgen. Wenn Ihre Erweiterungsmethode die Lesbarkeit Ihres Codes verbessert, verwenden Sie sie!

+2

Ich fühle mich wie die drei "TEST" + i.ToString ("00000") 'pro Append würde die Zeit, die es braucht, um es an einen' StringBuilder' zu hängen, Zwerg, und sollte wahrscheinlich mit konstanten Strings statt laufen, nur um zu bekommen ein besseres Bild. – dlras2

+0

Großer Test übrigens! –

+0

Beweise - immer gut. – ChrisF

2

Aus Klarheit Sicht Ihre Erweiterung ist in Ordnung.

Es wäre wahrscheinlich am besten, einfach die .append verwenden (x) .append (y) .append (z) Format, wenn Sie nie mehr als etwa 5 oder 6 Artikel.

String selbst würde nur net Sie ein Performance-Gewinn, wenn Sie viele Tausende von Artikeln wurden verarbeitet. Außerdem erstellen Sie das Array jedes Mal, wenn Sie die Methode aufrufen.

Also, wenn Sie es für Klarheit tun, das ist in Ordnung. Wenn Sie es aus Effizienzgründen tun, dann sind Sie wahrscheinlich auf dem Holzweg.

1

Ich habe kürzlich nicht getestet, aber in der Vergangenheit war StringBuilder tatsächlich langsamer als Plain-Vanilla-String-Verkettung ("this" + "das"), bis Sie zu etwa 7 Verkettungen kommen.

Wenn dieser String-Verkettung, die nicht in einer Schleife geschieht, können Sie zu prüfen, ob Sie überhaupt die Stringbuilder verwenden sollten. (In einer Schleife, beginne ich über Zuweisungen mit Plain-Vanilla-String-Verkettung zu kümmern, da Strings unveränderlich sind.)

+0

+1 für die Verkettung, guter Punkt! –

0

Chris,

Inspiriert von this Jon Skeet response (zweite Antwort), ich Ihren Code leicht neu geschrieben. Im Wesentlichen habe ich die TestRunner-Methode hinzugefügt, die die übergebene Funktion ausführt und die verstrichene Zeit meldet, wodurch ein wenig redundanter Code eliminiert wird. Nicht um selbstzufrieden zu sein, sondern eher als Programmierübung für mich. Ich hoffe, es ist hilfreich.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 

namespace SBTest 
{ 
    class Program 
    { 
    private static void Main(string[] args) 
    { 
     // JIT everything 
     AppendTest(1); 
     AppendFormatTest(1); 

     int iterations = 1000000; 

     // Run Tests 
     TestRunner(AppendTest, iterations); 
     TestRunner(AppendFormatTest, iterations); 

     Console.ReadLine(); 
    } 

    private static void TestRunner(Func<int, long> action, int iterations) 
    { 
     GC.Collect(); 

     var sw = Stopwatch.StartNew(); 
     long length = action(iterations); 
     sw.Stop(); 

     Console.WriteLine("--------------------- {0} -----------------------", action.Method.Name); 
     Console.WriteLine("iterations: {0:n0}", iterations); 
     Console.WriteLine("milliseconds: {0:n0}", sw.ElapsedMilliseconds); 
     Console.WriteLine("output length: {0:n0}", length); 
     Console.WriteLine(""); 
    } 

    private static long AppendTest(int iterations) 
    { 
     var sb = new StringBuilder(); 

     for (var i = 0; i < iterations; i++) 
     { 
     sb.Append("TEST" + i.ToString("00000"), 
        "TEST" + (i + 1).ToString("00000"), 
        "TEST" + (i + 2).ToString("00000")); 
     } 

     return sb.Length; 
    } 

    private static long AppendFormatTest(int iterations) 
    { 
     var sb = new StringBuilder(); 

     for (var i = 0; i < iterations; i++) 
     { 
     sb.AppendFormat("{0}{1}{2}", 
      "TEST" + i.ToString("00000"), 
      "TEST" + (i + 1).ToString("00000"), 
      "TEST" + (i + 2).ToString("00000")); 
     } 

     return sb.Length; 
    } 
    } 

    public static class SBExtentions 
    { 
    public static void Append(this StringBuilder sb, params string[] args) 
    { 
     foreach (var arg in args) 
     sb.Append(arg); 
    } 
    } 
} 

Hier ist der Ausgang:

--------------------- AppendTest ----------------------- 
iterations: 1,000,000 
milliseconds: 1,274 
output length: 29,700,006 

--------------------- AppendFormatTest ----------------------- 
iterations: 1,000,000 
milliseconds: 1,381 
output length: 29,700,006 
1

Potentiell noch schneller, da es höchstens eine Neuzuteilung/Kopierschritt, für viele Appends führt.

public void Append(this StringBuilder stringBuilder, params string[] args) 
{ 
    int required = stringBuilder.Length; 
    foreach (string arg in args) 
     required += arg.Length; 
    if (stringBuilder.Capacity < required) 
     stringBuilder.Capacity = required; 
    foreach (string arg in args) 
     stringBuilder.Append(arg); 
} 
+0

Ordentlich, aber einige Zeitpläne, um zu beweisen, dass es nett wäre. – Blorgbeard

Verwandte Themen