2010-08-13 16 views
52

Empecé a usar StringBuilder con preferencia a la concatenación recta, pero parece que falta un método crucial. Por lo tanto, he implementado por mí mismo, como una extensión:¿Estoy minando la eficiencia de StringBuilder?

public void Append(this StringBuilder stringBuilder, params string[] args) 
{ 
    foreach (string arg in args) 
     stringBuilder.Append(arg); 
} 

Esto convierte el siguiente desastre:

StringBuilder sb = new StringBuilder(); 
... 
sb.Append(SettingNode); 
sb.Append(KeyAttribute); 
sb.Append(setting.Name); 

En esto:

sb.Append(SettingNode, KeyAttribute, setting.Name); 

que podría utilizar sb.AppendFormat("{0}{1}{2}",..., pero esto parece incluso menos preferido, y aún más difícil de leer. ¿Mi extensión es un buen método, o de alguna manera socava los beneficios de StringBuilder? No estoy tratando de optimizar algo prematuramente, ya que mi método es más sobre la legibilidad que la velocidad, pero también me gustaría saber que no me estoy disparando en el pie.

+7

Debería señalar que a menos que ya tenga un StringBuilder, el concat de cadena recta sería más rápido. p.ej. cadena s = "orig" + SettingNode + KeyAttribute + setting.Name; – hemp

+0

@Hemp: solo ligeramente. Se deshace de la llamada a Anexar. Todo lo demás debería ser igual. Y eso suponiendo que no haya otros añadidos. –

+0

@Hemp: ¿Qué pasa con string s = string.Concat ("orig", SettingNode, KeyAttribute, setting.Name), creo que sería más rápido que usar "+" para concat. –

Respuesta

69

No veo ningún problema con su extensión. Si funciona para ti, todo está bien.

yo mismo préféré:

sb.Append(SettingNode) 
    .Append(KeyAttribute) 
    .Append(setting.Name); 
+24

+1 ¡Hazlo de esta manera! Si lo escribieron para devolver 'this', entonces esto es completamente lo que pretendían los diseñadores. –

+1

fluido o fluido – kenny

+2

@kenny ¿Estás preguntando cómo se llama ese estilo? AFAIK, se lo conoce como "interfaces fluidas". –

3

Aparte de un poco de sobrecarga, personalmente no veo ningún problema. Definitivamente más legible. Mientras pase una cantidad razonable de params, no veo el problema.

1

yo no diría que está minando su eficiencia, sino que puede estar haciendo algo ineficaz cuando un método más eficiente disponible. AppendFormat es lo que creo que quieres aquí. Si la cadena {0} {1} {2} que se usa constantemente es demasiado fea, tiendo a poner mis cadenas de formato en constrastes arriba, por lo que el aspecto sería más o menos el mismo que su extensión.

sb.AppendFormat(SETTING_FORMAT, var1, var2, var3); 
+1

No sé si AppendFormat es más eficiente. Claro, llama 'Append' una vez, pero bajo el capó puede estar haciendo' StringBuildinger.Append (String.Format (...)) ', que está instanciando una cadena adicional. Por supuesto, es una conjetura de cualquier forma hasta que alguien se desarme y vea cómo se ve realmente el IL – STW

+0

@STW: para estar seguro de que no puedo decir que lo sé, solo pensaría que un string.format es más eficiente que tres sb. Se agrega ... No sé ... –

+0

Me inclinaría a ir con AppendFormat, particularmente si la cadena se muestra al usuario; si la cadena necesita formatearse (por ejemplo, para la localización), no necesita cambiar su código. –

0

En última instancia, todo se reduce a la creación de cuerdas. Tengo la sensación de que la extensión dará como resultado un conteo de cuerdas más alto que con el formato de cadena. Pero el rendimiento probablemente no sea tan diferente.

9

Es un poco excesivo crear una matriz adicional, pero dudo que sea mucho. Debe medir

Si resulta significativo que la sobrecarga de crear matrices de cadenas es significativa, puede mitigarlo teniendo varias sobrecargas, una para dos parámetros, uno para tres, uno para cuatro, etc. solo cuando llegue a un número mayor de parámetros (por ejemplo, seis o siete) necesitará crear la matriz. Las sobrecargas serían así:

public void Append(this builder, string item1, string item2) 
{ 
    builder.Append(item1); 
    builder.Append(item2); 
} 

public void Append(this builder, string item1, string item2, string item3) 
{ 
    builder.Append(item1); 
    builder.Append(item2); 
    builder.Append(item3); 
} 

public void Append(this builder, string item1, string item2, 
        string item3, string item4) 
{ 
    builder.Append(item1); 
    builder.Append(item2); 
    builder.Append(item3); 
    builder.Append(item4); 
} 

// etc 

Y entonces uno de sobrecarga final utilizando params, por ejemplo,

public void Append(this builder, string item1, string item2, 
        string item3, string item4, params string[] otherItems) 
{ 
    builder.Append(item1); 
    builder.Append(item2); 
    builder.Append(item3); 
    builder.Append(item4); 
    foreach (string item in otherItems) 
    { 
     builder.Append(item); 
    } 
} 

sin duda me esperar que estos (o simplemente el método de extensión original) para ser más rápido que utilizando AppendFormat - que necesita para analizar la cadena de formato, después de todo.

Tenga en cuenta que no hice estas sobrecargas se llaman entre sí de forma recursiva pseudo-- Yo sospecho estarían entre líneas, pero si no lo eran la sobrecarga de la creación de un nuevo marco de pila, etc. podría terminar siendo significativo. (Estamos asumiendo que la sobrecarga de la matriz es significativa, si llegamos hasta aquí.)

+0

Visual Studio parece querer usar 'params string [] args' en lugar de' string arg0, string arg1', etc. ¿Tengo que hacer 'string arg0, string arg1, ..., params string [] moreArgs' para hacer que esto funcione? – dlras2

+0

@Jon Sin ofender, pero las múltiples sobrecargas se ven atroces. Al menos conviértalo en una llamada de regresión para cada sobrecarga adicional, y ¿por qué no usar params string [] args? –

+3

@Lucas: El objetivo de utilizar algunas sobrecargas con múltiples parámetros es evitar la sobrecarga de la matriz que se crea cada vez que utiliza un método con un parámetro 'params'. Sí, puede ser exagerado, pero esta es una pregunta de rendimiento, de ahí la respuesta. (Dije al principio que no sería demasiado sobrecargado). En cuanto a hacer una llamada otra, supongo que no me dolería * si * está siendo en línea ... pero de lo contrario es un marco de pila extra cada vez. –

32

Preguntas como esta siempre se pueden responder con un simple caso de prueba.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 

namespace SBTest 
{ 
    class Program 
    { 
     private const int ITERATIONS = 1000000; 

     private static void Main(string[] args) 
     { 
      Test1(); 
      Test2(); 
      Test3(); 
     } 

     private static void Test1() 
     { 
      var sw = Stopwatch.StartNew(); 
      var sb = new StringBuilder(); 

      for (var i = 0; i < ITERATIONS; i++) 
      { 
       sb.Append("TEST" + i.ToString("00000"), 
          "TEST" + (i + 1).ToString("00000"), 
          "TEST" + (i + 2).ToString("00000")); 
      } 

      sw.Stop(); 
      Console.WriteLine("Testing Append() extension method..."); 
      Console.WriteLine("--------------------------------------------"); 
      Console.WriteLine("Test 1 iterations: {0:n0}", ITERATIONS); 
      Console.WriteLine("Test 1 milliseconds: {0:n0}", sw.ElapsedMilliseconds); 
      Console.WriteLine("Test 1 output length: {0:n0}", sb.Length); 
      Console.WriteLine(""); 
     } 

     private static void Test2() 
     { 
      var sw = Stopwatch.StartNew(); 
      var sb = new StringBuilder(); 

      for (var i = 0; i < ITERATIONS; i++) 
      { 
       sb.Append("TEST" + i.ToString("00000")); 
       sb.Append("TEST" + (i+1).ToString("00000")); 
       sb.Append("TEST" + (i+2).ToString("00000")); 
      } 

      sw.Stop();  
      Console.WriteLine("Testing multiple calls to Append() built-in method..."); 
      Console.WriteLine("--------------------------------------------"); 
      Console.WriteLine("Test 2 iterations: {0:n0}", ITERATIONS); 
      Console.WriteLine("Test 2 milliseconds: {0:n0}", sw.ElapsedMilliseconds); 
      Console.WriteLine("Test 2 output length: {0:n0}", sb.Length); 
      Console.WriteLine(""); 
     } 

     private static void Test3() 
     { 
      var sw = Stopwatch.StartNew(); 
      var sb = new StringBuilder(); 

      for (var i = 0; i < ITERATIONS; i++) 
      { 
       sb.AppendFormat("{0}{1}{2}", 
        "TEST" + i.ToString("00000"), 
        "TEST" + (i + 1).ToString("00000"), 
        "TEST" + (i + 2).ToString("00000")); 
      } 

      sw.Stop(); 
      Console.WriteLine("Testing AppendFormat() built-in method..."); 
      Console.WriteLine("--------------------------------------------");    
      Console.WriteLine("Test 3 iterations: {0:n0}", ITERATIONS); 
      Console.WriteLine("Test 3 milliseconds: {0:n0}", sw.ElapsedMilliseconds); 
      Console.WriteLine("Test 3 output length: {0:n0}", sb.Length); 
      Console.WriteLine(""); 
     } 
    } 

    public static class SBExtentions 
    { 
     public static void Append(this StringBuilder sb, params string[] args) 
     { 
      foreach (var arg in args) 
       sb.Append(arg); 
     } 
    } 
} 

En mi PC, la salida es:

Testing Append() extension method... 
-------------------------------------------- 
Test 1 iterations: 1,000,000 
Test 1 milliseconds: 1,080 
Test 1 output length: 29,700,006 

Testing multiple calls to Append() built-in method... 
-------------------------------------------- 
Test 2 iterations: 1,000,000 
Test 2 milliseconds: 1,001 
Test 2 output length: 29,700,006 

Testing AppendFormat() built-in method... 
-------------------------------------------- 
Test 3 iterations: 1,000,000 
Test 3 milliseconds: 1,124 
Test 3 output length: 29,700,006 

Así que su método de extensión es sólo ligeramente más lento que el método Append() y es ligeramente más rápido que el método AppendFormat(), pero en todos 3 casos, la diferencia es demasiado trivial para preocuparse. Por lo tanto, si su método de extensión mejora la legibilidad de su código, úselo.

+2

Siento que los tres '" PRUEBA "+ i.ToString (" 00000 ")' por apéndice reducirían el tiempo necesario para agregarlo a un 'StringBuilder', y probablemente se deberían ejecutar con cadenas constantes, solo para obtener una mejor imagen. – dlras2

+0

¡Gran prueba por cierto! –

+0

Evidencia - siempre es bueno. – ChrisF

2

Desde una perspectiva de claridad, su extensión está bien.

Probablemente sea mejor simplemente usar el formato .append (x) .append (y) .append (z) si nunca tiene más de 5 o 6 elementos.

StringBuilder en sí mismo solo generaría una ganancia de rendimiento si estuviera procesando muchos miles de elementos. Además, crearás la matriz cada vez que llames al método.

Así que si lo haces por claridad, está bien. Si lo haces por eficiencia, entonces probablemente estés en el camino equivocado.

1

No he probado recientemente, pero en el pasado, StringBuilder era realmente más lento que la concatenación de cadenas simples ("this" + "that") hasta que obtenía unas 7 concatenaciones.

Si esto es una concatenación de cadenas que no está sucediendo en un bucle, es posible que desee considerar si debe utilizar StringBuilder en absoluto. (En un bucle, comienzo a preocuparse de las asignaciones con llano-vainilla concatenación de cadenas, ya que las cadenas son inmutables.)

+0

+1 para concatenación, buen punto! –

0

Chris,

Inspirado por this Jon Skeet response (segunda respuesta), Reescribí ligeramente su código. Básicamente, agregué el método TestRunner que ejecuta la función transferida e informa el tiempo transcurrido, eliminando un pequeño código redundante. No ser engreído, sino más bien como un ejercicio de programación para mí. Espero que sea útil.

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Diagnostics; 

namespace SBTest 
{ 
    class Program 
    { 
    private static void Main(string[] args) 
    { 
     // JIT everything 
     AppendTest(1); 
     AppendFormatTest(1); 

     int iterations = 1000000; 

     // Run Tests 
     TestRunner(AppendTest, iterations); 
     TestRunner(AppendFormatTest, iterations); 

     Console.ReadLine(); 
    } 

    private static void TestRunner(Func<int, long> action, int iterations) 
    { 
     GC.Collect(); 

     var sw = Stopwatch.StartNew(); 
     long length = action(iterations); 
     sw.Stop(); 

     Console.WriteLine("--------------------- {0} -----------------------", action.Method.Name); 
     Console.WriteLine("iterations: {0:n0}", iterations); 
     Console.WriteLine("milliseconds: {0:n0}", sw.ElapsedMilliseconds); 
     Console.WriteLine("output length: {0:n0}", length); 
     Console.WriteLine(""); 
    } 

    private static long AppendTest(int iterations) 
    { 
     var sb = new StringBuilder(); 

     for (var i = 0; i < iterations; i++) 
     { 
     sb.Append("TEST" + i.ToString("00000"), 
        "TEST" + (i + 1).ToString("00000"), 
        "TEST" + (i + 2).ToString("00000")); 
     } 

     return sb.Length; 
    } 

    private static long AppendFormatTest(int iterations) 
    { 
     var sb = new StringBuilder(); 

     for (var i = 0; i < iterations; i++) 
     { 
     sb.AppendFormat("{0}{1}{2}", 
      "TEST" + i.ToString("00000"), 
      "TEST" + (i + 1).ToString("00000"), 
      "TEST" + (i + 2).ToString("00000")); 
     } 

     return sb.Length; 
    } 
    } 

    public static class SBExtentions 
    { 
    public static void Append(this StringBuilder sb, params string[] args) 
    { 
     foreach (var arg in args) 
     sb.Append(arg); 
    } 
    } 
} 

Aquí está la salida:

--------------------- AppendTest ----------------------- 
iterations: 1,000,000 
milliseconds: 1,274 
output length: 29,700,006 

--------------------- AppendFormatTest ----------------------- 
iterations: 1,000,000 
milliseconds: 1,381 
output length: 29,700,006 
1

potencialmente aún más rápido, ya que realiza como máximo el paso uno reasignación/copia, para muchos APPENDs.

public void Append(this StringBuilder stringBuilder, params string[] args) 
{ 
    int required = stringBuilder.Length; 
    foreach (string arg in args) 
     required += arg.Length; 
    if (stringBuilder.Capacity < required) 
     stringBuilder.Capacity = required; 
    foreach (string arg in args) 
     stringBuilder.Append(arg); 
} 
+0

Limpio, pero algunos tiempos para demostrar que sería bueno. – Blorgbeard