Es String.Formato tan eficiente como StringBuilder


Supongamos que tengo un stringbuilder 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 tan eficiente o más eficiente que 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 tanto por cuál era más rápido en concatenar una cadena, pero que es más rápido en inyección una cuerda en otra.

En ambos casos arriba quiero inyectar una o más cadenas en el centro de una cadena de plantilla predefinida.

Perdón por la confusión

Author: James McMahon, 2008-08-09

12 answers

String.Format utiliza un StringBuilder internamente:

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, trate de benchmarking y / o perfiles de los dos para obtener una comparación adecuada.

Este tipo, Jerry Dixon, hizo algunos benchmarking:

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

Actualizado:

Tristemente el enlace anterior ha muerto desde entonces. Sin embargo, todavía hay una copia en la Máquina de Regreso:

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

Al final del día depende si tu cadena el formato va a ser llamado repetitivamente, es decir, estás haciendo un procesamiento de texto serio sobre 100 de megabytes de texto, o si se está llamando cuando un usuario hace clic en un botón de vez en cuando. A menos que estés haciendo un gran trabajo de procesamiento por lotes, me quedaría con String.Formato, ayuda a la legibilidad del código. Si sospecha un cuello de botella de perf, pegue un generador de perfiles en su código y vea dónde está realmente.

 138
Author: Kev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-07 17:59:06

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 la que se produce una asignación de memoria. Una operación de concatenación de cadenas siempre asigna memoria, mientras que una operación de concatenación de 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 un número fijo de Los objetos String se concatenan. En ese caso, las operaciones de concatenación individuales podrían incluso combinarse en una sola operación por el compilador. Un objeto StringBuilder es preferible para una operación de concatenación si se concatenan un número arbitrario de cadenas; por ejemplo, si un bucle concatena un número aleatorio de cadenas de entrada del usuario.

 43
Author: Greg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-09 14:36:45

Corrí algunos puntos de referencia de rendimiento rápido, y para 100,000 operaciones promediadas en 10 corridas, el primer método (Generador de cadenas) 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, entonces es posible que desee utilizar el primer método.

 12
Author: Vaibhav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-09 19:34:51

Esperaría Cadena.Formatear para ser más lento - tiene que analizar la cadena y luego concatenar.

Un par de notas:

  • Format es el camino a seguir para cadenas visibles por el usuario en aplicaciones profesionales; esto evita errores de localización
  • Si conoce de antemano la longitud de la cadena resultante, utilice el constructor StringBuilder(Int32) para predefinir la capacidad
 10
Author: McDowell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-09 17:09:10

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

He encontrado que, en los casos en que estoy construyendo cadenas de una manera bastante lineal, ya sea haciendo concatenaciones rectas o utilizando StringBuilder es su mejor opción. Sugiero esto en los casos en que la mayoría de la cadena que estás construyendo es dinámico. Dado que muy poco del texto es estático, lo más importante es que esté claro dónde se está poniendo cada pieza de texto dinámico en caso de que necesite actualizarse en el futuro.

Por otro lado, si estás hablando de un gran trozo de texto estático con dos o tres variables en él, incluso si es un poco menos eficiente, creo que la claridad que obtiene de la cadena.El formato hace que valga la pena. Usé esto a principios de esta semana cuando tuve que colocar un poco de dinámica texto en el centro de un documento de 4 páginas. Será más fácil actualizar esa gran porción de texto si está en una sola pieza que tener que actualizar tres piezas que concatenas juntas.

 8
Author: saalon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-09 14:40:00

Aunque solo sea porque string.El formato no hace exactamente lo que podría pensar, aquí hay una repetición de las pruebas 6 años más tarde en Net45.

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

Milisegundos, un millón de iteraciones:

  • Concatenación: 367
  • Nuevo StringBuilder para cada tecla: 452
  • Constructor de cadenas en caché: 419
  • cadena.Formato: 475

La lección que me llevo es que la diferencia de rendimiento es trivial y por lo tanto no debería detenerte a escribir el código legible más simple que puedas. Que para 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);
 7
Author: Chris F Carroll,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-07 18:00:17

Cadena.Usos del formato StringBuilder internally...so lógicamente, eso lleva a la idea de que sería un poco menos eficiente debido a más gastos generales. Sin embargo, una concatenación de cadena simple es el método más rápido de inyectar una cadena entre dos others...by un grado significativo. Esta evidencia fue demostrada por Rico Mariani en su primer Concurso de Rendimiento, hace años. El simple hecho es que las concatenaciones...cuando se conoce el número de partes de cadena (sin limitación..usted podría concatenar un miles parts...as siempre y cuando usted sabe que sus siempre 1000 partes)...son siempre más rápidos que StringBuilder o String.Formato. Se pueden realizar con una sola asignación de memoria y una serie de copias de memoria. Aquí es la prueba

Y aquí está el código real para alguna cadena.Métodos Concat, que en última instancia llaman a FillStringChecked que utiliza punteros para copiar memoria (extraída a través de 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);
        }
    }
}

Entonces:

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

Disfrute!

 5
Author: jrista,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-07 17:59:44

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

string cat = "cat";
string s = "The " + cat + " in the hat";
 3
Author: Vaibhav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-09 19:51:24

Realmente depende. Para cadenas pequeñas con pocas concatenaciones, es más rápido añadir las cadenas.

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

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

 0
Author: Joseph Daigle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-09 14:31:51

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

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

 0
Author: GateKiller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-08-09 16:29:55

Realmente depende de su patrón de uso.
Un punto de referencia detallado entre string.Join, string,Concat y string.Format se puede encontrar aquí: Cadena.El formato No es Adecuado para el Registro Intensivo

 0
Author: Liran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-08-01 18:40:40

Yo sugeriría no, desde String.El formato no fue diseñado para la 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);
 -1
Author: GateKiller,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-16 07:19:40