Pregunta

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);
¿Fue útil?

Solución

Es imposible decir, sin saber el tamaño de string1 y string2.

Con la llamada a AppendFormat , será una asignación previa de la memoria intermedia sólo una vez dada la longitud de la cadena de formato y las cuerdas que se insertarán y luego concatenar todo y la inserta en el búfer. Por muy grandes cadenas, esto será ventajoso con respecto a las llamadas separadas a Append que puede provocar el tampón para ampliar varias veces.

Sin embargo, las tres llamadas a Append podría o no podría desencadenar el crecimiento de la memoria intermedia y el registro de entrada se realiza cada llamada. Si las cadenas son lo suficientemente pequeños y no hay expansión memoria intermedia se activa, entonces será más rápido que el llamado a AppendFormat, ya que no tendrá que analizar la cadena de formato para averiguar dónde hacer los reemplazos.

Se necesita

Más datos para una respuesta definitiva

Debe tenerse en cuenta que hay poca discusión sobre el uso de la estática método Concat en la clase String ( de Jon respuesta utilizando AppendWithCapacity me recordó esto). Sus resultados muestran que para ser el mejor de los casos (suponiendo que no tiene que tomar ventaja de especificador de formato específico). String.Concat hace lo mismo en que va a predeterminar la longitud de las cadenas a concatenar y asignar previamente la memoria intermedia (con ligeramente más sobrecarga debido a construcciones en bucle a través de los parámetros). Su rendimiento va a ser comparable al método AppendWithCapacity de Jon.

O, simplemente, el operador de suma sencilla, ya que se compila en una llamada a String.Concat de todos modos, con la advertencia de que todas las adiciones se encuentran 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 colocación de código de prueba

Tiene que ejecutar los casos de prueba en separada carreras (o, al menos, lleve a cabo un GC entre la medición de prueba independiente se ejecuta). La razón de esto es que si lo que diga, 1.000.000 de carreras, la creación de un nuevo StringBuilder en cada iteración del bucle para una prueba, a continuación, ejecutar la siguiente prueba que se repite el mismo número de veces, la creación de un 1.000.000 instancias adicionales / StringBuilder, la GC más que probable que intervenir durante la segunda prueba y obstaculizar su temporización.

Otros consejos

casperOne es correcta . Una vez que alcanzan un cierto umbral, el método Append() se reduce por debajo AppendFormat(). Aquí están las diferentes longitudes y garrapatas transcurrido 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 las cadenas con una longitud de cerca de 20.000, la función se AppendFormat() ligeramente Append() promedio del mercado.

¿Por qué sucede esto? Ver de casperOne respuesta .

Editar

I reran cada prueba individualmente bajo configuración de lanzamiento y actualizados los resultados.

casperOne es totalmente exacto en que depende de los datos.Sin embargo, supongamos que está escribiendo esto como una biblioteca de clases para que la consuman terceros, ¿cuál usaría?

Una opción sería obtener lo mejor de ambos mundos: calcular cuántos datos realmente tendrás que agregar y luego usar StringBuilder.EnsureCapacity para asegurarnos de que solo necesitamos un único cambio de tamaño del búfer.

si no lo fuera también Aunque me molesta, usaría Append x3: parece "más probable" que sea más rápido, ya que analizar los tokens de formato de cadena en cada llamada es claramente un trabajo.

Tenga en cuenta que le pedí al equipo de BCL una especie de "formateador en caché" que podríamos crear usando una cadena de formato y luego reutilizar repetidamente.Es una locura que el marco tenga que analizar la cadena de formato cada vez que se usa.

EDITAR:Bien, edité un poco el código de John para mayor flexibilidad y agregué un "AppendWithCapacity" que calcula primero la capacidad necesaria.Estos son 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 tiempos están en milisegundos.

Lamentablemente, las tablas realmente no funcionan en SO.Las longitudes eran 1, 1000, 10000, 20000.

Veces:

  • Adjuntar:162, 475, 7997, 17970
  • Agregar formato:392, 499, 8541, 18993
  • Agregar con capacidad:139, 189, 1558, 3085

Dio la casualidad de que nunca vi que AppendFormat venciera a Append, pero hizo veamos a AppendWithCapacity ganar 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);
    }
}

Append será más rápida en la mayoría de los casos, porque hay muchas sobrecargas a ese método que permita que el compilador para llamar al método correcto. Dado que está utilizando el Strings StringBuilder puede utilizar la sobrecarga de String para Append.

AppendFormat toma un String y a continuación, un Object[] lo que significa que el formato tendrá que ser analizado y cada Object en la matriz tendrán que ser ToString'd antes de que pueda ser añadido a la matriz interna StringBuilder's.

Nota: Para el punto de casperOne - es difícil dar una respuesta exacta, sin más datos

.

StringBuilder APPENDs también ha cascada: Append() devuelve el StringBuilder en sí, por lo que puede escribir el código como el siguiente:

StringBuilder sb = new StringBuilder();
sb.Append(string1)
  .Append("----")
  .Append(string2);

Limpio, y genera menos IL-código (aunque eso es realmente un micro-optimización).

Por supuesto perfil de saber con seguridad en cada caso.

Dicho esto, creo que en general, será el primero porque no se está analizando varias veces la cadena de formato.

Sin embargo, la diferencia sería muy pequeño. Hasta el punto de que realmente debería considerar el uso de AppendFormat en la mayoría de los casos de todos modos.

Me asumir que era la llamada que hizo la menor cantidad de trabajo. Anexar simplemente concatena cadenas, donde AppendFormat está haciendo sustituciones de cadenas. Por supuesto, en estos días, nunca se sabe ...

1 debe ser más rápido porque está simplemente añadiendo las cuerdas mientras que 2 tiene que crear una cadena basada en un formato y luego añadir la cadena. Así que hay un paso adicional en ese país.

Cuanto más rápido, 1 en su caso, sin embargo, no es una comparación justa. Usted debe preguntar a StringBuilder.AppendFormat () vs StringBuilder.Append (string.Format ()) -., Donde el primero es más rápido debido al trabajo interno con matriz de caracteres

La segunda opción es más legible sin embargo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top