String.Format è efficiente come StringBuilder


160

Supponiamo di avere un costruttore di stringhe in C # che faccia questo:

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

sarebbe altrettanto efficiente o più efficiente di avere:

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

Se è così, perché?

MODIFICARE

Dopo alcune risposte interessanti, ho capito che probabilmente avrei dovuto essere un po 'più chiaro in quello che stavo chiedendo. Non stavo chiedendo quale fosse il più veloce a concatenare una stringa, ma quale sia più veloce nell'iniezione di una stringa in un'altra.

In entrambi i casi sopra voglio iniettare una o più stringhe nel mezzo di una stringa di modello predefinita.

Dispiace per la confusione


Si prega di lasciarli aperti per consentire futuri miglioramenti.
Mark Biek,

4
In uno scenario di casi speciali, il più veloce non è nessuno di questi: se la parte da sostituire è di dimensioni uguali alla nuova parte, è possibile modificare la stringa sul posto. Sfortunatamente, questo richiede una riflessione o un codice non sicuro e viola deliberatamente l'immutabilità della stringa. Non è una buona pratica, ma se la velocità è un problema ... :)
Abel

nell'esempio dato sopra string s = "The "+cat+" in the hat";potrebbe essere il più veloce a meno che non sia usato in un loop, nel qual caso il più veloce sarà con un StringBuilder inizializzato al di fuori del loop.
Surya Pratap,

Risposte:


146

NOTA: questa risposta è stata scritta quando .NET 2.0 era la versione corrente. Questo potrebbe non essere più applicabile alle versioni successive.

String.Formatutilizza StringBuilderinternamente:

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

Il codice sopra è uno snippet di mscorlib, quindi la domanda diventa "è StringBuilder.Append()più veloce di StringBuilder.AppendFormat()"?

Senza benchmarking probabilmente direi che l'esempio di codice sopra sarebbe eseguito più rapidamente usando .Append(). Ma è un'ipotesi, prova il benchmarking e / o la profilazione dei due per ottenere un confronto adeguato.

Questo ragazzo, Jerry Dixon, ha fatto alcuni benchmark:

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

aggiornato:

Purtroppo il link sopra è da allora morto. Tuttavia c'è ancora una copia sulla Way Back Machine:

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

Alla fine della giornata dipende se la formattazione della stringa verrà chiamata ripetutamente, ovvero se stai eseguendo una seria elaborazione del testo su centinaia di megabyte di testo o se viene chiamata quando un utente fa clic su un pulsante di tanto in tanto. A meno che tu non stia facendo un grosso lavoro di elaborazione batch, rimarrei con String.Format, aiuta la leggibilità del codice. Se sospetti un collo di bottiglia perfetto, attacca un profiler al tuo codice e vedi dove si trova davvero.


8
Un problema con i benchmark sulla pagina di Jerry Dixon è che non chiama mai .ToString()l' StringBuilderoggetto. In moltissime iterazioni, quel tempo fa una grande differenza e significa che non sta paragonando le mele alle mele. Questo è il motivo per cui mostra prestazioni così grandi StringBuildere probabilmente spiega la sua sorpresa. Ho appena ripetuto il benchmark correggendo quell'errore e ho ottenuto i risultati attesi: l' String +operatore è stato il più veloce, seguito da StringBuilder, con il String.Formatsollevamento del posteriore.
Ben Collins,

5
6 anni dopo, non è più così. In Net4, string.Format () crea e memorizza nella cache un'istanza StringBuilder che riutilizza, quindi in alcuni casi di test potrebbe essere più veloce di StringBuilder. Ho inserito un benchmark rivisto nella risposta di seguito (che dice ancora che concat è più veloce e per il mio caso di test, il formato è del 10% più lento di StringBuilder).
Chris F Carroll,

45

Dalla documentazione MSDN :

Le prestazioni di un'operazione di concatenazione per un oggetto String o StringBuilder dipendono dalla frequenza con cui si verifica un'allocazione di memoria. Un'operazione di concatenazione di stringhe alloca sempre la memoria, mentre un'operazione di concatenazione di StringBuilder alloca memoria solo se il buffer dell'oggetto StringBuilder è troppo piccolo per contenere i nuovi dati. Di conseguenza, la classe String è preferibile per un'operazione di concatenazione se viene concatenato un numero fisso di oggetti String. In tal caso, le singole operazioni di concatenazione potrebbero anche essere combinate in un'unica operazione dal compilatore. Un oggetto StringBuilder è preferibile per un'operazione di concatenazione se viene concatenato un numero arbitrario di stringhe; ad esempio, se un ciclo concatena un numero casuale di stringhe di input dell'utente.


12

Ho eseguito alcuni rapidi benchmark delle prestazioni e per 100.000 operazioni in media su 10 corse, il primo metodo (String Builder) richiede quasi la metà del secondo (String Format).

Quindi, se questo è raro, non importa. Ma se è un'operazione comune, allora potresti voler usare il primo metodo.


10

Mi aspetto che String.Format sia più lento: deve analizzare la stringa e quindi concatenarla.

Coppia di note:

  • Il formato è la strada da percorrere per stringhe visibili dall'utente in applicazioni professionali; questo evita bug di localizzazione
  • Se si conosce in anticipo la lunghezza della stringa risultante, utilizzare il costruttore StringBuilder (Int32) per predefinire la capacità

8

Penso che in molti casi come questa chiarezza, e non l'efficienza, dovrebbe essere la tua più grande preoccupazione. A meno che tu non stia schiacciando tonnellate di corde o costruendo qualcosa per un dispositivo mobile a bassa potenza, questo probabilmente non ti farà molto male alla velocità di corsa.

Ho scoperto che, nei casi in cui sto costruendo stringhe in modo abbastanza lineare, fare le concatenazioni diritte o usare StringBuilder è l'opzione migliore. Lo consiglio nei casi in cui la maggior parte della stringa che stai creando è dinamica. Poiché molto poco del testo è statico, la cosa più importante è che è chiaro dove ogni pezzo di testo dinamico viene messo nel caso in cui debba essere aggiornato in futuro.

D'altra parte, se stai parlando di un grosso pezzo di testo statico con due o tre variabili, anche se è un po 'meno efficiente, penso che la chiarezza che ottieni dalla stringa. Formattata ne valga la pena. L'ho usato all'inizio di questa settimana quando ho dovuto posizionare un po 'di testo dinamico al centro di un documento di 4 pagine. Sarà più facile aggiornare quel grosso pezzo di testo se è in un pezzo piuttosto che dover aggiornare tre pezzi che concatenate insieme.


Sì! Usa String.Format quando ha senso farlo, ad esempio quando stai formattando le stringhe. Utilizzare la concatenazione di stringhe o StringBuilder quando si esegue la concatenazione meccanica. Cerca sempre di scegliere il metodo che comunica le tue intenzioni al prossimo manutentore.
Rob,

8

Se non altro perché string.Format non fa esattamente quello che potresti pensare, ecco una ripetizione dei test 6 anni dopo su Net45.

Concat è ancora più veloce, ma in realtà ha una differenza inferiore al 30%. StringBuilder e Format differiscono appena del 5-10%. Ho avuto alcune variazioni del 20% durante l'esecuzione dei test.

Millisecondi, un milione di iterazioni:

  • Concatenazione: 367
  • Nuovo stringBuilder per ogni chiave: 452
  • StringBuilder memorizzato nella cache: 419
  • string.Format: 475

La lezione che tengo è che la differenza di prestazioni è banale e quindi non dovrebbe impedirti di scrivere il codice leggibile più semplice che puoi. Quale per i miei soldi è spesso ma non sempre 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
Con "string.Format non fa esattamente quello che potresti pensare", intendo che nel codice sorgente 4.5 tenta di creare e riutilizzare un'istanza StringBuilder memorizzata nella cache. Quindi ho incluso questo approccio nel test
Chris F Carroll, il

6

String.Format utilizza StringBuilderinternamente ... in modo logico che porta all'idea che sarebbe un po 'meno performante a causa di un sovraccarico. Tuttavia, una semplice concatenazione di stringhe è il metodo più veloce per iniettare una stringa tra altre due ... in misura significativa. Questa prova è stata dimostrata da Rico Mariani nel suo primo Performance Quiz, anni fa. Il fatto semplice è che le concatenazioni ... quando è noto il numero di parti di stringa (senza limitazione ... potresti concatenare un migliaio di parti ... purché tu sappia che sono sempre 1000 parti) ... sono sempre più veloci di StringBuildero String. Formato. Possono essere eseguiti con una singola allocazione di memoria e una serie di copie di memoria. Ecco la prova

Ed ecco il codice effettivo per alcuni metodi String.Concat, che alla fine chiamano FillStringChecked che utilizza i puntatori per copiare la memoria (estratto tramite 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);
        }
    }
}

Allora:

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

Godere!


in Net4, string.Format memorizza nella cache e riutilizza un'istanza di StringBuilder, pertanto in alcuni usi potrebbe essere più veloce.
Chris F Carroll,

3

Oh, anche il più veloce sarebbe:

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

no, la concatenazione di stringhe è estremamente lenta, poiché .NET crea copie extra delle variabili di stringa tra le operazioni di concat, in questo caso: due copie extra più la copia finale per l'assegnazione. Risultato: prestazioni estremamente scarse rispetto alle StringBuilderquali viene realizzato per ottimizzare questo tipo di codifica in primo luogo.
Abele

Forse il più veloce da scrivere;)
UpTheCreek

2
@Abel: la risposta potrebbe non avere dettagli, ma questo approccio è l'opzione più veloce, in questo esempio particolare. Il compilatore lo trasformerà in una singola chiamata String.Concat (), quindi la sostituzione con StringBuilder rallenterà effettivamente il codice.
Dan C.

1
@Vaibhav è corretto: in questo caso, la concatenazione è la più veloce. Naturalmente, la differenza sarebbe insignificante se non fosse ripetuta moltissime volte, o forse operata su una stringa molto, molto più grande.
Ben Collins,

0

Dipende davvero. Per stringhe di piccole dimensioni con poche concatenazioni, in realtà è più veloce solo aggiungere le stringhe.

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

Ma per stringhe più grandi (stringhe molto molto grandi), è quindi più efficiente usare StringBuilder.


0

In entrambi i casi sopra voglio iniettare una o più stringhe nel mezzo di una stringa di modello predefinita.

Nel qual caso, suggerirei String.Format è il più veloce perché è progettato per quello scopo esatto.



-1

Suggerirei di no, poiché String.Format non è stato progettato per la concatenazione, è stato progettato per la formattazione dell'output di vari input come una data.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.