¿Es String.Format tan eficiente como StringBuilder?

160

Supongamos que tengo un generador de cadenas en C # que hace esto:

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

sería eso tan eficiente o más eficiente como tener:

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

Si es así, ¿por qué?

EDITAR

Después de algunas respuestas interesantes, me di cuenta de que probablemente debería haber sido un poco más claro en lo que estaba preguntando. No estaba preguntando cuál era más rápido para concatenar una cadena, sino cuál era más rápido para inyectar una cadena en otra.

En los dos casos anteriores, quiero inyectar una o más cadenas en el medio de una cadena de plantilla predefinida.

Perdón por la confusion

lomaxx
fuente
Déjelos abiertos para permitir futuras mejoras.
Mark Biek el
44
En un escenario de caso especial, el más rápido no es ninguno de estos: si la parte a reemplazar tiene el mismo tamaño que la parte nueva, puede cambiar la cadena en su lugar. Desafortunadamente, esto requiere reflexión o código inseguro y viola deliberadamente la inmutabilidad de la cadena. No es una buena práctica, pero si la velocidad es un problema ... :)
Abel el
en el ejemplo dado anteriormente string s = "The "+cat+" in the hat";podría ser el más rápido a menos que se use en un bucle, en cuyo caso el más rápido será con un StringBuilder inicializado fuera del bucle.
Surya Pratap

Respuestas:

146

NOTA: Esta respuesta se escribió cuando .NET 2.0 era la versión actual. Es posible que esto ya no se aplique a versiones posteriores.

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

El código anterior es un fragmento de mscorlib, por lo que la pregunta se convierte en "¿es StringBuilder.Append()más rápido que StringBuilder.AppendFormat()"?

Sin benchmarking, probablemente diría que el ejemplo de código anterior se ejecutaría más rápidamente usando .Append(). Pero es una suposición, intente hacer una evaluación comparativa y / o perfilar los dos para obtener una comparación adecuada.

Este tipo, Jerry Dixon, hizo algunos puntos de referencia:

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

Actualizado:

Lamentablemente, el enlace anterior ha muerto desde entonces. Sin embargo, todavía hay una copia en Way Back Machine:

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

Al final del día, depende de si su formato de cadena se llamará repetidamente, es decir, si está haciendo un procesamiento de texto serio de más de 100 megabytes de texto, o si se llama cuando un usuario hace clic en un botón de vez en cuando. A menos que esté haciendo un gran trabajo de procesamiento por lotes, me quedaría con String.Format, ayuda a la legibilidad del código. Si sospecha que hay un cuello de botella de perf, pegue un perfilador en su código y vea dónde está realmente.

Kev
fuente
8
Un problema con los puntos de referencia en la página de Jerry Dixon es que él nunca recurre .ToString()al StringBuilderobjeto. Durante muchas iteraciones, ese tiempo hace una gran diferencia y significa que no está comparando manzanas con manzanas. Esa es la razón por la que muestra un rendimiento tan bueno StringBuildery probablemente explica su sorpresa. Yo sólo repitió el punto de referencia corregir ese error y dieron los resultados esperados: el String +operador fue el más rápido, seguido por StringBuilder, con String.Formatla retaguardia.
Ben Collins
55
6 años después, esto ya no es así. En Net4, string.Format () crea y almacena en caché una instancia de StringBuilder que reutiliza, por lo que en algunos casos de prueba podría ser más rápido que StringBuilder. Puse un punto de referencia revisado en respuesta a continuación (que todavía dice que concat es más rápido y para mi caso de prueba, el formato es 10% más lento que StringBuilder).
Chris F Carroll
45

De la documentación de MSDN :

El rendimiento de una operación de concatenación para un objeto String o StringBuilder depende de la frecuencia con que se produce una asignación de memoria. Una operación de concatenación String siempre asigna memoria, mientras que una operación de concatenación StringBuilder solo asigna memoria si el búfer de objetos StringBuilder es demasiado pequeño para acomodar los nuevos datos. En consecuencia, la clase String es preferible para una operación de concatenación si se concatena un número fijo de objetos String. En ese caso, el compilador puede incluso combinar las operaciones de concatenación individuales en una sola operación. Un objeto StringBuilder es preferible para una operación de concatenación si se concatena un número arbitrario de cadenas; por ejemplo, si un ciclo concatena un número aleatorio de cadenas de entrada del usuario.

Greg
fuente
12

Ejecuté algunos puntos de referencia de rendimiento rápidos, y para 100,000 operaciones con un promedio de más de 10 ejecuciones, el primer método (String Builder) toma casi la mitad del tiempo del segundo (Formato de cadena).

Entonces, si esto es infrecuente, no importa. Pero si es una operación común, es posible que desee utilizar el primer método.

Vaibhav
fuente
10

Esperaría que String.Format sea ​​más lento: tiene que analizar la cadena y luego concatenarla.

Un par de notas:

  • El formato es el camino a seguir para cadenas visibles para el usuario en aplicaciones profesionales; esto evita errores de localización
  • Si conoce de antemano la longitud de la cadena resultante, use el constructor StringBuilder (Int32) para predefinir la capacidad
McDowell
fuente
8

Creo que en la mayoría de los casos como esta claridad, y no la eficiencia, debería ser su mayor preocupación. A menos que esté aplastando toneladas de cuerdas, o construyendo algo para un dispositivo móvil de menor potencia, esto probablemente no afectará mucho su velocidad de carrera.

Descubrí que, en los casos en que estoy construyendo cadenas de forma bastante lineal, ya sea hacer concatenaciones rectas o usar StringBuilder es su mejor opción. Sugiero esto en casos donde la mayoría de la cadena que está creando es dinámica. Como muy poco del texto es estático, lo más importante es que esté claro dónde se coloca cada parte del texto dinámico en caso de que necesite actualizarse en el futuro.

Por otro lado, si estás hablando de una gran porción de texto estático con dos o tres variables, incluso si es un poco menos eficiente, creo que la claridad que obtienes de string.Format hace que valga la pena. Utilicé esto a principios de esta semana cuando tuve que colocar un poco de texto dinámico en el centro de un documento de 4 páginas. Será más fácil actualizar ese gran fragmento de texto si está en una sola pieza que tener que actualizar tres piezas que concatenan juntas.

saalon
fuente
¡Si! Utilice String.Format cuando tenga sentido hacerlo, es decir, cuando formatee cadenas. Utilice la concatenación de cadenas o un StringBuilder cuando realice la concatenación mecánica. Esfuércese siempre por elegir el método que comunique su intención al siguiente responsable.
Rob
8

Aunque solo sea porque string.Format no hace exactamente lo que podrías pensar, aquí hay una repetición de las pruebas 6 años después en Net45.

Concat sigue siendo el más rápido, pero en realidad es menos del 30% de diferencia. StringBuilder y Format difieren apenas en un 5-10%. Obtuve variaciones del 20% ejecutando las pruebas varias veces.

Milisegundos, un millón de iteraciones:

  • Concatenación: 367
  • Nuevo stringBuilder para cada clave: 452
  • StringBuilder en caché: 419
  • string.Format: 475

La lección que extraigo es que la diferencia de rendimiento es trivial y, por lo tanto, no debería impedir que escribas el código legible más simple que puedas. Que por mi dinero es a menudo pero no siempre 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);
Chris F Carroll
fuente
2
Por "string.Format no hace exactamente lo que podría pensar" quiero decir que en el código fuente 4.5 intenta crear y reutilizar una instancia de StringBuilder en caché. Así que incluí ese enfoque en la prueba
Chris F Carroll, el
6

String.Format usa StringBuilderinternamente ... tan lógicamente que lleva a la idea de que sería un poco menos eficiente debido a una mayor sobrecarga. Sin embargo, una simple concatenación de cadenas es el método más rápido de inyectar una cadena entre otras dos ... en un grado significativo. Rico Mariani demostró esta evidencia en su primer Performance Quiz, hace años. El hecho simple es que las concatenaciones ... cuando se conoce el número de partes de la cadena (sin limitación ... podría concatenar mil partes ... siempre y cuando sepa que siempre son 1000 partes) ... siempre son más rápidas que StringBuildero String. Formato. Se pueden realizar con una sola asignación de memoria y una serie de copias de memoria. aquí esta la prueba

Y aquí está el código real para algunos métodos String.Concat, que finalmente llaman a FillStringChecked que usa punteros para copiar la memoria (extraída mediante 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);
        }
    }
}

Por lo que entonces:

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

¡Disfrutar!

jrista
fuente
en Net4, string.Format almacena en caché y reutiliza una instancia de StringBuilder, por lo que en algunos usos puede ser más rápido.
Chris F Carroll
3

Oh también, el más rápido sería:

string cat = "cat";
string s = "The " + cat + " in the hat";
Vaibhav
fuente
no, la concatenación de cadenas es extremadamente lenta, porque .NET crea copias adicionales de sus variables de cadena entre las operaciones de concat, en este caso: dos copias adicionales más la copia final para la asignación. Resultado: rendimiento extremadamente pobre en comparación con el StringBuilderque se hace para optimizar este tipo de codificación en primer lugar.
Abel el
Más rápido de escribir, tal vez;)
UpTheCreek
2
@Abel: La respuesta podría ser la falta de detalles, pero este enfoque ES la opción más rápida, en este ejemplo en particular. El compilador transformará esto en una sola llamada String.Concat (), por lo que reemplazarlo por un StringBuilder en realidad ralentizará el código.
Dan C.
1
@Vaibhav es correcto: en este caso, la concatenación es la más rápida. Por supuesto, la diferencia sería insignificante a menos que se repita muchas veces, o tal vez opere sobre una cadena mucho, mucho más grande.
Ben Collins
0

Realmente depende Para cadenas pequeñas con pocas concatenaciones, en realidad es más rápido solo agregar las cadenas.

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

Pero para cadenas más grandes (cadenas muy muy grandes), es más eficiente usar StringBuilder.

Joseph Daigle
fuente
0

En los dos casos anteriores, quiero inyectar una o más cadenas en el medio de una cadena de plantilla predefinida.

En cuyo caso, sugeriría String.Format es el más rápido porque está diseñado para ese propósito exacto.

GateKiller
fuente
-1

Sugeriría que no, ya que String.Format no fue diseñado para concatenación, fue diseñado para formatear la salida de varias entradas, como una fecha.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
GateKiller
fuente