2008-08-09 7 views
144

Supongamos que tengo un StringBuilder en C# que hace esto:¿Es String.Format tan eficiente como StringBuilder

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

habría que ser tan eficiente o más eficiente porque tiene:

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 que se dieron cuenta de que probablemente debería haber sido un poco más claro en lo que estaba pidiendo. No estaba tanto preguntando qué era más rápido para concatenar una cadena, pero que es más rápido en inyectando una cadena en otra.

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

Lo siento por la confusión

+0

Por favor, deje esto abierto para permitir futuras mejoras. –

+3

En un escenario de caso especial, el más rápido no es ninguno de estos: si la parte que se va a reemplazar tiene el mismo tamaño que la parte nueva, puede cambiar la cadena en contexto. 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

+0

en el ejemplo anterior '' 'string s =" El "+ cat +" en el sombrero ";' '' 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 '' 'StringBuilder' '' inicializado fuera del ciclo. –

Respuesta

136

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, así que la pregunta se convierte en "StringBuilder.Append() es más rápido que StringBuilder.AppendFormat()"?

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

Este cap, Jerry Dixon, hizo un poco de evaluación comparativa:

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 el 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 cadenas va a ser llamado de forma repetitiva, es decir, que está haciendo un serio procesamiento de texto 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 leer el código. Si sospecha que hay un cuello de botella perf entonces pegue un perfilador en su código y vea dónde está realmente.

+6

Un problema con los puntos de referencia en la página de Jerry Dixon es que nunca llama a '.ToString()' en el objeto 'StringBuilder'. En 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 para 'StringBuilder' y probablemente explica su sorpresa. Acabo de repetir el punto de referencia para corregir ese error y obtuve los resultados esperados: el operador 'String'' + 'fue el más rápido, seguido por' StringBuilder', con 'String.Format' saliendo por la parte trasera. –

+5

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 podría ser, en algunos casos de prueba, más rápido que StringBuilder. He puesto un punto de referencia revisado en respuesta a continuación (que todavía dice que concat es el más rápido y para mi caso de prueba, el formato es 10% más lento que StringBuilder). –

-2

yo no sugerir, desde String.Format no fue diseñado para la concatenación, era de diseño para el formato de la salida de diversos insumos tales como una fecha.

String s = String.Format("Today is {0:dd-MMM-yyyy}.", DateTime.Today); 
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"; 

Sin embargo, para la cadena más grande (muy, muy grandes cadenas), es entonces más eficiente usar StringBuilder.

43

Desde el MSDN documentation:

La realización de una operación de concatenación de un objeto String o StringBuilder depende de con qué frecuencia 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 StringBuilder solo asigna memoria si el búfer de objetos StringBuilder es demasiado pequeño para acomodar los datos nuevos. 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, las operaciones de concatenación individuales incluso podrían combinarse en una única 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.

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.

He descubierto que, en los casos en que estoy creando cadenas de una manera bastante lineal, hacer concatenaciones rectas o utilizar StringBuilder es su mejor opción. Sugiero esto en los casos en que la mayoría de la cadena que estás 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 fragmento de texto dinámico en caso de que sea necesario actualizarlo en el futuro.

Por otro lado, si está hablando de una gran cantidad de texto estático con dos o tres variables, aunque sea un poco menos eficiente, creo que la claridad que obtiene de la cadena. El formato hace que valga la pena eso. Lo usé a principios de esta semana cuando tuve que colocar un pedacito de texto dinámico en el centro de un documento de 4 páginas. Será más fácil actualizar ese gran trozo de texto si está en una sola pieza que tener que actualizar tres piezas que concatenas juntas.

+0

¡Sí! Utilice String.Format cuando tenga sentido hacerlo, es decir, cuando esté formateando cadenas. Utilice la concatenación de cadenas o un StringBuilder cuando realice una concatenación mecánica. Siempre trate de elegir el método que comunique su intención al próximo mantenedor. – Rob

-1

En ambos casos, deseo insertar 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.

9

yo esperaría String.Format a ser más lento - que tiene que analizar la cadena y luego concatenarlo.

par de notas:

  • Formato es el camino a seguir para las cadenas visibles para el usuario en las aplicaciones profesionales; esto evita errores de localización
  • Si conoce la longitud de la cadena resultante de antemano, utilizar el StringBuilder (Int32) constructor para predefinir la capacidad
11

me encontré con algunas pruebas de rendimiento rápido, y para 100.000 operaciones promedio durante 10 ejecuciones, el primer método (generador de cadenas) toma casi la mitad del tiempo del segundo (formato de cadena).

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

3

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

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

no, la concatenación de cadenas es extremadamente lenta, porque .NET crea copias adicionales de las variables de cadena entre las operaciones concat, en este caso: dos copias adicionales más la copia final de la tarea. Resultado: rendimiento extremadamente pobre en comparación con 'StringBuilder' que está hecho para optimizar este tipo de codificación en primer lugar. – Abel

+0

Lo más rápido posible para escribir;) – UpTheCreek

+2

@Abel: La respuesta puede carecer 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 con un StringBuilder ralentizará el código. –

5

String.Format utiliza internamente StringBuilder ... por lo que lógicamente que conduce a la idea de que sería un poco menos eficiente debido a un mayor gasto. 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 primera prueba de rendimiento, hace años. El hecho simple es que concatenaciones ... cuando se conoce el número de partes de la secuencia (sin limitación ... podrías concatenar mil partes ... siempre que 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. Here es la prueba

Y aquí es el código real para algunos métodos String.Concat, que en última instancia se llaman FillStringChecked que utiliza punteros de memoria para copiar (extraído a través del 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!"; 

¡Disfrútalo!

+0

en Net4, string.Format almacena en caché y reutiliza una instancia de StringBuilder, por lo que en algunos casos puede ser más rápido. –

6

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

Concat es aún más rápido, pero en realidad es menos del 30% de diferencia. StringBuilder y el formato difieren en apenas un 5-10%. Obtuve variaciones del 20% ejecutando las pruebas algunas veces.

milisegundos, un millón de iteraciones:

  • concatenación: 367
  • new StringBuilder para cada tecla: 452
  • caché de StringBuilder: 419
  • cadena.Formato: 475

La lección me llevo es que la diferencia de rendimiento es trivial y lo que no debe dejar de escribir el código legible por más simple que pueda. 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); 
+2

Por "string.Format no hace exactamente lo que podría pensar" Me refiero a que en el código fuente 4.5 intenta crear y reutilizar una instancia de StringBuilder almacenada en caché. Así que incluí ese enfoque en la prueba –

Cuestiones relacionadas