Pregunta

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 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 cuál era más rápido al concatenar una cadena, sino cuál era más rápido al concatenar una cadena. inyectando una cuerda 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

¿Fue útil?

Solución

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.Format usa 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 es "es StringBuilder.Append() más rápido que StringBuilder.AppendFormat()"?

Sin una evaluación comparativa, 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 realizar una evaluación comparativa y/o un perfil de los dos para obtener una comparación adecuada.

Este tipo, Jerry Dixon, hizo algunas evaluaciones comparativas:

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

Actualizado:

Lamentablemente, el enlace anterior ya no está disponible.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 el formato de su cadena se llamará repetitivamente, es decir,está realizando un procesamiento de texto serio de más de cientos de 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, ya que ayuda a la legibilidad del código.Si sospecha que hay un cuello de botella en el rendimiento, coloque un generador de perfiles en su código y vea dónde está realmente.

Otros consejos

Desde el 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 String siempre asigna memoria, mientras que una operación de concatenación de StringBuilder solo asigna memoria si el búfer del objeto 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 podría incluso combinar las operaciones de concatenación individuales en una sola operación.Es preferible un objeto StringBuilder para una operación de concatenación si se concatena un número arbitrario de cadenas;por ejemplo, si un bucle concatena un número aleatorio de cadenas de entrada del usuario.

Ejecuté algunas pruebas comparativas de rendimiento rápidas y, para un promedio de 100 000 operaciones en 10 ejecuciones, el primer método (String Builder) toma casi la mitad del tiempo que el segundo (String Format).

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

Yo esperaría Cadena.Formato para ser más lento: tiene que analizar la cadena y entonces concatenarlo.

Un par de notas:

  • 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, utilice el Constructor de cadenas (Int32) constructor para predefinir la capacidad

Aunque sólo sea porque string.Format no hace exactamente lo que podría 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 tiene menos del 30% de diferencia.StringBuilder y Format difieren apenas entre un 5 y un 10%.Obtuve variaciones del 20% al ejecutar las pruebas varias veces.

Milisegundos, un millón de iteraciones:

  • Concatenación:367
  • Nuevo stringBuilder para cada clave:452
  • Generador 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 impedirte escribir el código legible más simple que puedas.Lo cual, por mi dinero, es frecuente 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);

Creo que en la mayoría de 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 afectará mucho tu velocidad de carrera.

Descubrí que, en los casos en los que construyo cadenas de una manera bastante lineal, la mejor opción es hacer concatenaciones directas o usar StringBuilder.Sugiero esto en los casos en los que la mayor parte de la cadena que estás construyendo es dinámica.Dado que muy poco texto es estático, lo más importante es que esté claro dónde se coloca cada fragmento de texto dinámico en caso de que sea necesario actualizarlo 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 con string.Format hace que valga la pena.Utilicé esto a principios de esta semana cuando tuve que colocar un fragmento de texto dinámico 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.

Usos de String.Format StringBuilder internamente... tan lógicamente eso lleva a la idea de que tendría un poco menos de rendimiento debido a más gastos generales.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.Esta evidencia fue demostrada por Rico Mariani 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... puedes concatenar mil partes... siempre y cuando sepas que siempre son 1000 partes)... siempre son más rápidas que StringBuilder o Cadena.Formato.Se pueden realizar con una única asignación de memoria y una serie de copias de memoria. Aquí es 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 memoria (extraídos 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!

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

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

Realmente depende.Para cadenas pequeñas con pocas concatenaciones, en realidad es más rápido simplemente 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.

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 que String.Format es el más rápido porque está diseñado para ese propósito exacto.

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í: String.Format no es adecuado para el registro intensivo

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

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top