2012-09-11 11 views
32

Estoy navegando por el código fuente del proyecto de código abierto SignalR, y veo this diff code que tiene derecho "No use StringBuilder o foreach en este código de acceso directo camino":"No use StringBuilder o foreach en esta ruta de código de acceso"

-   public static string MakeCursor(IEnumerable<Cursor> cursors) 
+   public static string MakeCursor(IList<Cursor> cursors) 
      { 
-    var sb = new StringBuilder(); 
-    bool first = true; 
-    foreach (var c in cursors) 
+    var result = ""; 
+    for (int i = 0; i < cursors.Count; i++) 
       { 
-     if (!first) 
+     if (i > 0) 
        { 
-      sb.Append('|'); 
+      result += '|'; 
        } 
-     sb.Append(Escape(c.Key)); 
-     sb.Append(','); 
-     sb.Append(c.Id); 
-     first = false; 
+     result += Escape(cursors[i].Key); 
+     result += ','; 
+     result += cursors[i].Id; 
       } 
-    return sb.ToString(); 
+    return result; 
      } 

entiendo por qué foreach podría ser menos eficiente en ocasiones, y por qué es reemplazado por de.

Sin embargo, aprendí y experimenté que StringBuilder es la forma más eficiente de concatenar cadenas. Así que me pregunto por qué el autor decidió reemplazarlo con concatenación estándar.

¿Qué hay de malo aquí y en general sobre el uso de StringBuilder?

+8

Qué le hace pensar que 'foreach' es menos eficiente que' for'? Depende de la implementación.Y sí, usar concatenación repetida de cadenas parece una idea terrible aquí, especialmente si hay muchos cursores. –

+1

Totalmente de acuerdo con Jon. Me sorprendería si la versión de StringBuilder tuviera un rendimiento peor que la versión de concatenación. Dependiendo del recuento de las iteraciones de bucle, podría ayudar a prevenir algunas inicializaciones de objetos implícitos "ocultos" si StringBuilder se inicializa con una capacidad de inicio mayor. ¿Has medido/perfilado ambas versiones para compararlas? –

+4

Deberías pedírselo a [@dfowler] (http://stackoverflow.com/users/45091/dfowler) :) –

Respuesta

30

Hice el cambio de código y sí, hizo una gran diferencia en el número de asignaciones (GetEnumerator()) llamadas vs no. Imagine que este código es millones de veces por segundo. La cantidad de enumeradores asignados es ridícula y se puede evitar.

edición: Ahora invertimos de control con el fin de evitar cualquier asignación (escribir al escritor directa): https://github.com/SignalR/SignalR/blob/2.0.2/src/Microsoft.AspNet.SignalR.Core/Messaging/Cursor.cs#L36

+0

David, ¿qué pasa? haciendo foreach pero aún usando StringBuilder? –

+2

Seguimiento aquí https://gist.github.com/3703926 – davidfowl

+0

Muchas gracias por tomarse el tiempo para construir un prototipo que nos muestre la prueba de los méritos de estos cambios. Si entiendo correctamente, un StringBuilder es más adecuado para manejar cadenas largas, en lugar de tratar el corto uno detrás del otro en un bucle largo. – Larry

1

¿Cuántas concatenaciones estás haciendo? Si es numeroso, use StringBuilder. Si solo unos pocos, la sobrecarga de crear StringBuilder superará cualquier ventaja.

+0

Nota que en este caso, solo necesitamos llegar a dos cursores para terminar con 7 concatenaciones de cadena, lo cual es bastante desagradable. –

+0

No necesariamente: con un tamaño desconocido, puede obtener múltiples reasignaciones del búfer interno. Eso puede ser costoso. – Oded

+0

¿No sería entonces el enfoque correcto mirar la cantidad de cursores y hacer una ruta (String) para muy pocos y la otra para muchos (StringBuilder)? –

3

Solo espero que el tipo que cambió esto realmente midiera la diferencia.

  • Hay una sobrecarga al crear instancias de un nuevo generador de cadenas cada vez. Esto también ejerce presión sobre la recolección de memoria/basura.
  • El compilador puede generar código 'stringbuilderlike' para concatenaciones simples
  • El PARA realidad podría ser más lenta, ya que podría requerir comprobación de límites que no se hace con foreach como los compiladores 'sabe' que estén dentro de los límites.
+2

Conociendo el proyecto y siguiendo su progreso en JabbR con David Fowler, estoy casi seguro de que esto es exactamente lo que provocó el cambio. Estaba pasando mucho tiempo midiendo con CLRProfiler y similares, y abordando toda la basura que se estaba creando. Con suerte, evaluará el hilo con detalles. –

1

voy a poner mi dinero en

  StringBuilder sb = new StringBuilder(); 
      bool first = true; 
      foreach (Cursor c in cursors) 
      { 
       if (first) 
       { 
        first = false; // only assign it once 
       } 
       else 
       { 
        sb.Append('|'); 
       } 
       sb.Append(Escape(c.Key) + ',' + c.Id); 
      } 
      return sb.ToString(); 

Pero yo pondría mi dinero y la actualización de dfowler. Mira el enlace en su respuesta.

2

Depende del número de Cursor proporcionados a la función.

La mayoría de las comparaciones entre los dos apporaches parecen favorecer StringBuilder sobre la concatinación de cuerdas cuando se concatenan 4-10 cuerdas. Lo más probable es que esté a favor de StringBuilder si no tuviera motivos explícitos (por ejemplo, comparación de rendimiento de los dos enfoques para mi problema/aplicación). Consideraría preasignar un buffer en el StringBuilder para evitar (muchas) reasignaciones.

Para más información sobre el tema, ver String concatenation vs String Builder. Performance y Concatenating with StringBuilders vs. Strings.

+1

Gracias por la información. Esta pregunta relacionada también es muy interesante: http://stackoverflow.com/questions/550702/at-what-point-does-using-a-stringbuilder-become-insignificant-or-an-overhead – Larry

Cuestiones relacionadas