Ist String.Format so effizient wie StringBuilder?


161

Angenommen, ich habe einen Stringbuilder in C #, der dies ausführt:

StringBuilder sb = new StringBuilder();
string cat = "cat";
sb.Append("the ").Append(cat).(" in the hat");
string s = sb.ToString();

Wäre das so effizient oder effizienter als:

string cat = "cat";
string s = String.Format("The {0} in the hat", cat);

Wenn ja warum?

BEARBEITEN

Nach einigen interessanten Antworten wurde mir klar, dass ich wahrscheinlich etwas klarer hätte sein sollen, was ich fragte. Ich habe nicht so sehr gefragt, welche schneller eine Saite verketten, aber welche schneller eine Saite in eine andere injizieren können .

In beiden oben genannten Fällen möchte ich eine oder mehrere Zeichenfolgen in die Mitte einer vordefinierten Vorlagenzeichenfolge einfügen.

Entschuldigung für die Verwirrung


Bitte lassen Sie diese offen, um zukünftige Verbesserungen zu ermöglichen.
Mark Biek

4
In einem speziellen Fall ist das schnellste keines der beiden: Wenn das zu ersetzende Teil gleich groß wie das neue Teil ist, können Sie die Zeichenfolge direkt ändern. Leider erfordert dies Reflexion oder unsicheren Code und verletzt absichtlich die Unveränderlichkeit der Zeichenfolge. Keine gute Übung, aber wenn Geschwindigkeit ein Problem ist ... :)
Abel

In dem oben angegebenen Beispiel ist es string s = "The "+cat+" in the hat";möglicherweise das schnellste, es sei denn, es wird in einer Schleife verwendet. In diesem Fall ist es am schnellsten, wenn es StringBuilder außerhalb der Schleife initialisiert wird.
Surya Pratap

Antworten:


146

HINWEIS: Diese Antwort wurde geschrieben, als .NET 2.0 die aktuelle Version war. Dies gilt möglicherweise nicht mehr für spätere Versionen.

String.Formatverwendet eine StringBuilderintern:

public static string Format(IFormatProvider provider, string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }

    StringBuilder builder = new StringBuilder(format.Length + (args.Length * 8));
    builder.AppendFormat(provider, format, args);
    return builder.ToString();
}

Der obige Code ist ein Ausschnitt aus mscorlib, daher lautet die Frage "ist StringBuilder.Append()schneller als StringBuilder.AppendFormat()"?

Ohne Benchmarking würde ich wahrscheinlich sagen, dass das obige Codebeispiel mit schneller ausgeführt werden würde .Append(). Aber es ist eine Vermutung, versuchen Sie, die beiden zu vergleichen und / oder zu profilieren, um einen richtigen Vergleich zu erhalten.

Dieser Kerl, Jerry Dixon, hat ein Benchmarking durchgeführt:

http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

Aktualisiert:

Leider ist der obige Link inzwischen gestorben. Es gibt jedoch noch eine Kopie auf der Way Back Machine:

http://web.archive.org/web/20090417100252/http://jdixon.dotnetdevelopersjournal.com/string_concatenation_stringbuilder_and_stringformat.htm

Letztendlich hängt es davon ab, ob Ihre Zeichenfolgenformatierung wiederholt aufgerufen wird, dh Sie führen eine ernsthafte Textverarbeitung über 100 Megabyte Text durch oder ob sie aufgerufen wird, wenn ein Benutzer ab und zu auf eine Schaltfläche klickt. Wenn Sie keine große Stapelverarbeitungsaufgabe ausführen, bleibe ich bei String.Format, da dies die Lesbarkeit des Codes verbessert. Wenn Sie einen Perf-Engpass vermuten, kleben Sie einen Profiler auf Ihren Code und sehen Sie, wo er wirklich ist.


8
Ein Problem mit den Benchmarks auf Jerry Dixons Seite ist, dass er .ToString()das StringBuilderObjekt niemals aufruft . Bei sehr vielen Iterationen macht diese Zeit einen großen Unterschied und bedeutet, dass er Äpfel nicht ganz mit Äpfeln vergleicht. Das ist der Grund, warum er so großartige Leistungen erbringt StringBuilderund wahrscheinlich für seine Überraschung verantwortlich ist. Ich wiederholte nur die Benchmark diesen Fehler zu korrigieren und bekam die erwarteten Ergebnisse: Der String +Betreiber wurde am schnellsten, gefolgt von StringBuilder, mit String.Formatder Nachhut.
Ben Collins

5
6 Jahre später ist das nicht mehr ganz so. In Net4 erstellt string.Format () eine StringBuilder-Instanz, die wiederverwendet wird, und speichert sie zwischen, sodass sie in einigen Testfällen möglicherweise schneller als StringBuilder ist. Ich habe unten einen überarbeiteten Benchmark als Antwort angegeben (der immer noch besagt, dass concat am schnellsten ist und für meinen Testfall das Format 10% langsamer ist als StringBuilder).
Chris F Carroll

45

Aus der MSDN-Dokumentation :

Die Leistung einer Verkettungsoperation für ein String- oder StringBuilder-Objekt hängt davon ab, wie oft eine Speicherzuweisung erfolgt. Eine String-Verkettungsoperation weist immer Speicher zu, während eine StringBuilder-Verkettungsoperation nur Speicher zuweist, wenn der StringBuilder-Objektpuffer zu klein ist, um die neuen Daten aufzunehmen. Folglich ist die String-Klasse für eine Verkettungsoperation vorzuziehen, wenn eine feste Anzahl von String-Objekten verkettet ist. In diesem Fall können die einzelnen Verkettungsoperationen vom Compiler sogar zu einer einzigen Operation kombiniert werden. Ein StringBuilder-Objekt ist für eine Verkettungsoperation vorzuziehen, wenn eine beliebige Anzahl von Zeichenfolgen verkettet ist. Zum Beispiel, wenn eine Schleife eine zufällige Anzahl von Zeichenfolgen von Benutzereingaben verkettet.


12

Ich habe einige schnelle Leistungsbenchmarks durchgeführt, und für 100.000 Operationen, die über 10 Läufe gemittelt wurden, dauert die erste Methode (String Builder) fast die Hälfte der zweiten (String-Format).

Wenn dies selten vorkommt, spielt es keine Rolle. Wenn es sich jedoch um eine häufige Operation handelt, möchten Sie möglicherweise die erste Methode verwenden.


10

Ich würde erwarten, dass String.Format langsamer ist - es muss den String analysieren und dann verketten.

Einige Anmerkungen:

  • Das Format ist der Weg für vom Benutzer sichtbare Zeichenfolgen in professionellen Anwendungen. Dies vermeidet Lokalisierungsfehler
  • Wenn Sie die Länge der resultierenden Zeichenfolge im Voraus kennen, verwenden Sie den StringBuilder- Konstruktor (Int32) , um die Kapazität vorab zu definieren

8

Ich denke, in den meisten Fällen sollte diese Klarheit und nicht die Effizienz Ihre größte Sorge sein. Wenn Sie nicht Tonnen von Saiten zerkleinern oder etwas für ein mobiles Gerät mit geringerer Leistung bauen, wird dies Ihre Laufgeschwindigkeit wahrscheinlich nicht wesentlich beeinträchtigen.

Ich habe festgestellt, dass in Fällen, in denen ich Strings ziemlich linear erstelle, entweder direkte Verkettungen oder die Verwendung von StringBuilder die beste Option sind. Ich schlage dies in Fällen vor, in denen der Großteil der von Ihnen erstellten Zeichenfolge dynamisch ist. Da nur sehr wenig Text statisch ist, ist das Wichtigste, dass klar ist, wo jedes dynamische Textstück abgelegt wird, falls es in Zukunft aktualisiert werden muss.

Auf der anderen Seite, wenn Sie über einen großen Teil statischen Textes mit zwei oder drei Variablen sprechen, auch wenn er etwas weniger effizient ist, denke ich, dass die Klarheit, die Sie durch string.Format gewinnen, es wert macht. Ich habe dies Anfang dieser Woche verwendet, als ich ein Stück dynamischen Text in die Mitte eines 4-seitigen Dokuments einfügen musste. Es ist einfacher, diesen großen Textblock zu aktualisieren, wenn er aus einem Stück besteht, als drei Teile zu aktualisieren, die Sie miteinander verketten.


Ja! Verwenden Sie String.Format, wenn dies sinnvoll ist, dh wenn Sie Zeichenfolgen formatieren. Verwenden Sie die String-Verkettung oder einen StringBuilder, wenn Sie eine mechanische Verkettung durchführen. Versuchen Sie immer, die Methode auszuwählen, die Ihre Absicht dem nächsten Betreuer mitteilt.
Rob

8

Wenn auch nur, weil string.Format nicht genau das tut, was Sie vielleicht denken, hier ist eine Wiederholung der Tests 6 Jahre später auf Net45.

Concat ist immer noch am schnellsten, aber es ist wirklich weniger als 30% Unterschied. StringBuilder und Format unterscheiden sich um kaum 5-10%. Ich habe einige Male Abweichungen von 20% bei den Tests festgestellt.

Millisekunden, eine Million Iterationen:

  • Verkettung: 367
  • Neuer stringBuilder für jeden Schlüssel: 452
  • Zwischengespeicherter StringBuilder: 419
  • string.Format: 475

Die Lektion, die ich wegnehme, ist, dass der Leistungsunterschied trivial ist und Sie daher nicht davon abhalten sollten, den einfachsten lesbaren Code zu schreiben, den Sie können. Was für mein Geld oft aber nicht immer ist a + b + c.

const int iterations=1000000;
var keyprefix= this.GetType().FullName;
var maxkeylength=keyprefix + 1 + 1+ Math.Log10(iterations);
Console.WriteLine("KeyPrefix \"{0}\", Max Key Length {1}",keyprefix, maxkeylength);

var concatkeys= new string[iterations];
var stringbuilderkeys= new string[iterations];
var cachedsbkeys= new string[iterations];
var formatkeys= new string[iterations];

var stopwatch= new System.Diagnostics.Stopwatch();
Console.WriteLine("Concatenation:");
stopwatch.Start();

for(int i=0; i<iterations; i++){
    var key1= keyprefix+":" + i.ToString();
    concatkeys[i]=key1;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("New stringBuilder for each key:");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2= new StringBuilder(keyprefix).Append(":").Append(i.ToString()).ToString();
    stringbuilderkeys[i]= key2;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("Cached StringBuilder:");
var cachedSB= new StringBuilder(maxkeylength);
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key2b= cachedSB.Clear().Append(keyprefix).Append(":").Append(i.ToString()).ToString();
    cachedsbkeys[i]= key2b;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

Console.WriteLine("string.Format");
stopwatch.Restart();

for(int i=0; i<iterations; i++){
    var key3= string.Format("{0}:{1}", keyprefix,i.ToString());
    formatkeys[i]= key3;
}

Console.WriteLine(stopwatch.ElapsedMilliseconds);

var referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway= concatkeys.Union(stringbuilderkeys).Union(cachedsbkeys).Union(formatkeys).LastOrDefault(x=>x[1]=='-');
Console.WriteLine(referToTheComputedValuesSoCompilerCantOptimiseTheLoopsAway);

2
Mit "string.Format macht nicht genau das, was Sie vielleicht denken" meine ich, dass es im 4.5-Quellcode versucht, eine zwischengespeicherte StringBuilder-Instanz zu erstellen und wiederzuverwenden. Also habe ich diesen Ansatz in den Test aufgenommen
Chris F Carroll

6

String.Format wird StringBuilderintern verwendet ... so logisch, dass die Idee entsteht, dass es aufgrund des höheren Overheads etwas weniger performant wäre. Eine einfache String-Verkettung ist jedoch die schnellste Methode, um einen String zwischen zwei anderen zu injizieren ... in erheblichem Maße. Dieser Beweis wurde von Rico Mariani vor Jahren in seinem allerersten Performance Quiz demonstriert. Einfache Tatsache ist, dass Verkettungen ... wenn die Anzahl der String-Teile bekannt ist (ohne Einschränkung ... Sie könnten tausend Teile verketten ... solange Sie wissen, dass es immer 1000 Teile sind) ... immer schneller als StringBuilderoder String sind. Format. Sie können mit einer einzelnen Speicherzuordnung und einer Reihe von Speicherkopien durchgeführt werden. Hier ist der Beweis

Und hier ist der eigentliche Code für einige String.Concat-Methoden, die letztendlich FillStringChecked aufrufen, das Zeiger zum Kopieren des Speichers verwendet (extrahiert über Reflector):

public static string Concat(params string[] values)
{
    int totalLength = 0;

    if (values == null)
    {
        throw new ArgumentNullException("values");
    }

    string[] strArray = new string[values.Length];

    for (int i = 0; i < values.Length; i++)
    {
        string str = values[i];
        strArray[i] = (str == null) ? Empty : str;
        totalLength += strArray[i].Length;

        if (totalLength < 0)
        {
            throw new OutOfMemoryException();
        }
    }

    return ConcatArray(strArray, totalLength);
}

public static string Concat(string str0, string str1, string str2, string str3)
{
    if (((str0 == null) && (str1 == null)) && ((str2 == null) && (str3 == null)))
    {
        return Empty;
    }

    if (str0 == null)
    {
        str0 = Empty;
    }

    if (str1 == null)
    {
        str1 = Empty;
    }

    if (str2 == null)
    {
        str2 = Empty;
    }

    if (str3 == null)
    {
        str3 = Empty;
    }

    int length = ((str0.Length + str1.Length) + str2.Length) + str3.Length;
    string dest = FastAllocateString(length);
    FillStringChecked(dest, 0, str0);
    FillStringChecked(dest, str0.Length, str1);
    FillStringChecked(dest, str0.Length + str1.Length, str2);
    FillStringChecked(dest, (str0.Length + str1.Length) + str2.Length, str3);
    return dest;
}

private static string ConcatArray(string[] values, int totalLength)
{
    string dest = FastAllocateString(totalLength);
    int destPos = 0;

    for (int i = 0; i < values.Length; i++)
    {
        FillStringChecked(dest, destPos, values[i]);
        destPos += values[i].Length;
    }

    return dest;
}

private static unsafe void FillStringChecked(string dest, int destPos, string src)
{
    int length = src.Length;

    if (length > (dest.Length - destPos))
    {
        throw new IndexOutOfRangeException();
    }

    fixed (char* chRef = &dest.m_firstChar)
    {
        fixed (char* chRef2 = &src.m_firstChar)
        {
            wstrcpy(chRef + destPos, chRef2, length);
        }
    }
}

Also dann:

string what = "cat";
string inthehat = "The " + what + " in the hat!";

Genießen!


In Net4 speichert string.Format eine StringBuilder-Instanz zwischen und verwendet sie erneut. In einigen Fällen kann dies schneller sein.
Chris F Carroll

3

Ach ja, das schnellste wäre:

string cat = "cat";
string s = "The " + cat + " in the hat";

Nein, die Verkettung von Zeichenfolgen ist extrem langsam, da .NET zwischen den Concat-Vorgängen zusätzliche Kopien Ihrer Zeichenfolgenvariablen erstellt. In diesem Fall: zwei zusätzliche Kopien plus die endgültige Kopie für die Zuweisung. Ergebnis: extrem schlechte Leistung, im Vergleich dazu StringBuilderwird diese Art der Codierung in erster Linie optimiert.
Abel

Am schnellsten zu tippen vielleicht;)
UpTheCreek

2
@Abel: Der Antwort fehlen möglicherweise Details, aber dieser Ansatz ist in diesem Beispiel die schnellste Option. Der Compiler wandelt dies in einen einzelnen String.Concat () -Aufruf um, sodass das Ersetzen durch einen StringBuilder den Code tatsächlich verlangsamt.
Dan C.

1
@ Vaibhav ist richtig: In diesem Fall ist die Verkettung am schnellsten. Natürlich wäre der Unterschied unbedeutend, wenn er nicht sehr oft wiederholt oder vielleicht über eine viel, viel größere Saite operiert würde.
Ben Collins

0

Es kommt wirklich darauf an. Bei kleinen Zeichenfolgen mit wenigen Verkettungen ist es tatsächlich schneller, nur die Zeichenfolgen anzuhängen.

String s = "String A" + "String B";

Bei größeren Zeichenfolgen (sehr, sehr große Zeichenfolgen) ist die Verwendung von StringBuilder jedoch effizienter.


0

In beiden oben genannten Fällen möchte ich eine oder mehrere Zeichenfolgen in die Mitte einer vordefinierten Vorlagenzeichenfolge einfügen.

In diesem Fall würde ich String.Format als das schnellste empfehlen, da es genau für diesen Zweck entwickelt wurde.



-1

Ich würde nicht vorschlagen, da String.Format nicht für die Verkettung konzipiert wurde, sondern für die Formatierung der Ausgabe verschiedener Eingaben, z. B. eines Datums.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.