practices - stringbuilder list c#
StringBuilder.Append Vs StringBuilder.AppendFormat (9)
1 debería ser más rápido porque simplemente agrega las cadenas mientras que 2 tiene que crear una cadena basada en un formato y luego agregar la cadena. Entonces hay un paso adicional allí.
Me preguntaba sobre StringBuilder y tengo una pregunta que esperaba que la comunidad pudiera explicar.
Olvidémonos de la legibilidad del código, ¿cuál de estos es más rápido y por qué?
StringBuilder.Append
:
StringBuilder sb = new StringBuilder();
sb.Append(string1);
sb.Append("----");
sb.Append(string2);
StringBuilder.AppendFormat
:
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}----{1}",string1,string2);
Asumiría que fue la llamada la que menos trabajo hizo. Append solo concatena cadenas, donde AppendFormat realiza sustituciones de cadenas. Por supuesto, en estos días, nunca se puede decir ...
Es imposible de decir, sin saber el tamaño de string1
y string2
.
Con la llamada a AppendFormat
, preasignará el búfer una vez dada la longitud de la cadena de formato y las cadenas que se insertarán, y luego concatenará todo e insertará en el búfer. Para cadenas muy grandes, esto será ventajoso en llamadas separadas a Append
que podría hacer que el búfer se expanda varias veces.
Sin embargo, las tres llamadas a Append
pueden desencadenar o no el crecimiento del búfer y esa verificación se realiza en cada llamada. Si las cadenas son lo suficientemente pequeñas y no se desencadena la expansión del búfer, será más rápido que la llamada a AppendFormat
porque no tendrá que analizar la cadena de formato para averiguar dónde hacer los reemplazos.
Se necesitan más datos para una respuesta definitiva
Cabe señalar que hay poca discusión sobre el uso del método estático de Concat
en la clase String
( la respuesta de Jon utilizando AppendWithCapacity
me lo recordó). Los resultados de sus pruebas muestran que es el mejor caso (suponiendo que no tenga que aprovechar el especificador de formato específico). String.Concat
hace lo mismo, ya que predeterminará la longitud de las cadenas para concatenar y preasignar el búfer (con un poco más de sobrecarga debido a las construcciones de bucle a través de los parámetros). Su rendimiento será comparable al método AppendWithCapacity
de Jon.
O simplemente, el operador de adición simple, ya que compila una llamada a String.Concat
todos modos, con la advertencia de que todas las adiciones están en la misma expresión:
// One call to String.Concat.
string result = a + b + c;
NO
// Two calls to String.Concat.
string result = a + b;
result = result + c;
Para todos aquellos que ponen código de prueba
Debe ejecutar sus casos de prueba en ejecuciones separadas (o al menos, realizar un GC entre la medición de las ejecuciones de prueba por separado). La razón de esto es que si dices, 1,000,000 ejecuciones, creando un nuevo StringBuilder
en cada iteración del ciclo para una prueba, y luego ejecutas la siguiente prueba que se repite el mismo número de veces, creando 1,000,000 instancias adicionales de StringBuilder
, Es muy probable que el GC intervenga durante la segunda prueba y dificulte su sincronización.
Más rápido es 1 en su caso, sin embargo, no es una comparación justa. Debería preguntar StringBuilder.AppendFormat () vs StringBuilder.Append (string.Format ()) - donde el primero es más rápido debido al trabajo interno con la matriz char.
Sin embargo, tu segunda opción es más legible.
Por supuesto perfil para saber con certeza en cada caso.
Dicho esto, creo que, en general, será el primero porque no está analizando repetidamente la cadena de formato.
Sin embargo, la diferencia sería muy pequeña. Hasta el punto de que realmente debería considerar usar AppendFormat
en la mayoría de los casos de todos modos.
CasperOne es completamente exacto de que depende de los datos . Sin embargo, suponga que está escribiendo esto como una biblioteca de clase para consumir de terceros, ¿cuál usaría?
Una opción sería obtener lo mejor de ambos mundos: calcular la cantidad de datos que realmente va a tener que agregar, y luego usar StringBuilder.EnsureCapacity para asegurarse de que solo necesitemos un solo tamaño de búfer.
Sin embargo, si no me molestara demasiado , usaría Append
x3; parece "más probable" que sea más rápido, ya que el análisis de los tokens de formato de cadena en cada llamada es claramente de "make-work".
Tenga en cuenta que le pedí al equipo de BCL una especie de "formateador en caché" que podríamos crear utilizando una cadena de formato y luego reutilizarlo repetidamente. Es una locura que el framework tenga que analizar la cadena de formato cada vez que se usa.
EDITAR: De acuerdo, he editado el código de John un tanto por la flexibilidad y he añadido un "AppendWithCapacity" que primero calcula la capacidad necesaria. Aquí están los resultados para las diferentes longitudes: para la longitud 1 utilicé 1,000,000 de iteraciones; para todas las demás longitudes utilicé 100.000. (Esto fue solo para obtener tiempos de ejecución razonables.) Todos los horarios están en milis.
Desafortunadamente, las tablas realmente no funcionan en SO. Las longitudes fueron 1, 1000, 10000, 20000
Veces:
- Adjuntar: 162, 475, 7997, 17970
- AppendFormat: 392, 499, 8541, 18993
- AppendWithCapacity: 139, 189, 1558, 3085
Así que, como sucedió, nunca vi AppendFormat beat Append, pero sí vi ganar AppendWithCapacity por un margen muy sustancial.
Aquí está el código completo:
using System;
using System.Diagnostics;
using System.Text;
public class StringBuilderTest
{
static void Append(string string1, string string2)
{
StringBuilder sb = new StringBuilder();
sb.Append(string1);
sb.Append("----");
sb.Append(string2);
}
static void AppendWithCapacity(string string1, string string2)
{
int capacity = string1.Length + string2.Length + 4;
StringBuilder sb = new StringBuilder(capacity);
sb.Append(string1);
sb.Append("----");
sb.Append(string2);
}
static void AppendFormat(string string1, string string2)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}----{1}", string1, string2);
}
static void Main(string[] args)
{
int size = int.Parse(args[0]);
int iterations = int.Parse(args[1]);
string method = args[2];
Action<string,string> action;
switch (method)
{
case "Append": action = Append; break;
case "AppendWithCapacity": action = AppendWithCapacity; break;
case "AppendFormat": action = AppendFormat; break;
default: throw new ArgumentException();
}
string string1 = new string(''x'', size);
string string2 = new string(''y'', size);
// Make sure it''s JITted
action(string1, string2);
GC.Collect();
Stopwatch sw = Stopwatch.StartNew();
for (int i=0; i < iterations; i++)
{
action(string1, string2);
}
sw.Stop();
Console.WriteLine("Time: {0}ms", (int) sw.ElapsedMilliseconds);
}
}
CasperOne es correcto . Una vez que alcanza un cierto umbral, el método Append()
se vuelve más lento que AppendFormat()
. Aquí están las diferentes longitudes y tics transcurridos de 100.000 iteraciones de cada método:
Longitud: 1
Append() - 50900
AppendFormat() - 126826
Longitud: 1000
Append() - 1241938
AppendFormat() - 1337396
Longitud: 10,000
Append() - 12482051
AppendFormat() - 12740862
Longitud: 20,000
Append() - 61029875
AppendFormat() - 60483914
Cuando se introducen cadenas con una longitud cercana a 20,000, la función AppendFormat()
superará ligeramente a Append()
.
¿Por qué pasó esto? Ver la respuesta de CasperOne .
Editar:
Volví a realizar cada prueba individualmente en Configuración de versión y actualicé los resultados.
Append
será más rápido en la mayoría de los casos porque hay muchas sobrecargas en ese método que le permiten al compilador llamar al método correcto. Como está utilizando Strings
StringBuilder
puede usar la sobrecarga String
para Append
.
AppendFormat
toma una String
y luego un Object[]
que significa que el formato tendrá que ser analizado y cada Object
en la matriz tendrá que ser ToString''d
antes de que pueda ser agregado a la matriz interna StringBuilder''s
.
Nota: Para el argumento de casperone: es difícil dar una respuesta exacta sin más datos.
StringBuilder
también tiene agregados en cascada: Append()
devuelve StringBuilder
, para que pueda escribir su código de esta manera:
StringBuilder sb = new StringBuilder();
sb.Append(string1)
.Append("----")
.Append(string2);
Limpio, y genera menos código IL (aunque eso es realmente una micro-optimización).