2010-02-01 9 views
19

OK, de modo que el compilador optimiza las concatenaciones de expresiones de cadena constantes en una cadena. Estupendo.¿Por qué String.Concat no está optimizado para StringBuilder.Append?

Ahora con la concatenación de cadena de cadenas solo conocida en tiempo de ejecución, ¿por qué el compilador no optimiza la concatenación de cadenas en bucles y concatenaciones de, digamos, más de 10 cadenas para usar StringBuilder.Append? Quiero decir, es posible, ¿verdad? Crea una instancia de StringBuilder y toma cada concatenación y conviértela en una llamada Append().

¿Hay alguna razón por la cual este debe o podríano ser optimizado? ¿Qué me estoy perdiendo?

+0

¿Qué es la concatenación de cadenas dinámicas? ¿Tienes un enlace? –

+1

Me refiero a la concatenación de cadenas dinámicas. Su longitud no se conoce en el momento de la compilación. –

Respuesta

39

La respuesta definitiva deberá venir del equipo de diseño del compilador. Pero déjame tomar una puñalada aquí ...

Si su pregunta es, ¿por qué el compilador no se enciende esto:

string s = ""; 
for(int i = 0; i < 100; i ++) 
    s = string.Concat(s, i.ToString()); 

en esto:

StringBuilder sb = new StringBuilder(); 
for(int i = 0; i < 100; i++) 
    sb.Append(i.ToString()); 
string s = sb.ToString(); 

La respuesta más probable es que esto no es una optimización. Esta es una reescritura del código que introduce nuevos constructos basados ​​en el conocimiento y la intención que tiene el desarrollador, no el compilador.

Este tipo de cambio requeriría que el compilador tenga más conocimiento del BCL de lo apropiado. ¿Qué pasa si mañana, algún servicio de ensamblaje de cuerdas más óptimo está disponible? ¿Debería el compilador usar eso?

¿Qué pasaría si las condiciones de su ciclo fueran más complicadas, en caso de que el compilador intente realizar algún análisis estático para decidir si el resultado de dicha reescritura aún sería funcionalmente equivalente? En muchos sentidos, esto sería como resolver el halting problem.

Finalmente, no estoy seguro de que en todos los casos esto resulte en un código de ejecución más rápida. Hay un costo al crear una instancia de StringBuilder y cambiar el tamaño de su búfer interno a medida que se agrega el texto. De hecho, el costo de agregar está fuertemente ligado al tamaño de la cadena que se concatena, cuántos hay, a qué se parece la presión de la memoria. Estas son cosas que el compilador no puede predecir de antemano.

Es su trabajo como desarrollador para escribir código de buen rendimiento. El compilador solo puede ayudar asegurando safe, optimizaciones que preservan invariablemente. No reescribes tu código por ti.

+0

Todos los puntos muy buenos, especialmente el relacionado con el BCL. –

+0

Haces que parezca imposible pensar en esto. De hecho, los últimos compiladores de Java tienen esta "optimización". Incluso se hace referencia en las especificaciones: http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.18.1.2 –

+2

Creo que hay una diferencia entre algo que es * posible *, y que algo sea *vale la pena*. En algunos usos estrechos, puede ser posible sustituir 'StringBuilder' (o enfoques similares) para optimizar la concatenación de cadenas.Pero en la práctica, dado que no siempre es posible y en algunos casos puede presentar consecuencias negativas, probablemente no sea una buena idea como optimización general. Por otro lado, tengo curiosidad si alguien sabe si algún compilador de Java ** realmente cumple ** la optimización de cadenas que describe el enlace anterior. – LBushkin

6

dos razones:

  • No se puede identificar mediante programación lugares en los que sería estrictamente mayor rendimiento.
  • La "optimización" disminuirá la velocidad si se realiza de forma incorrecta.

Puede sugerir a las personas que utilicen las llamadas correctas para su aplicación, pero en algún momento es responsabilidad del desarrollador hacer las cosas bien.

Editar: En cuanto al corte, tenemos otro par de problemas:

  • La única manera de saber con certeza que el corte se alcanza es el análisis de flujo complicada. El número de lugares donde esto podría encontrar secciones que podrían convertirse es extremadamente pequeño.
  • El análisis de flujo es costoso. Si lo hace en tiempo de ejecución, todo el programa se ejecutará más lentamente por la rara posibilidad de que una parte del código mal escrito sea más rápida. Si lo hace en tiempo de compilación, no es un error según la sintaxis del lenguaje, pero puede emitir una advertencia, y eso es exactamente lo que hace FXCop (una herramienta de análisis de flujo lenta pero disponible). Solo piense si FXCop siempre tuvo que funcionar con el compilador; tantas horas la gente estaría esperando para ejecutar el código. Y si fue en tiempo de ejecución, bienvenido a los tiempos de inicio de JVM ...
+0

Bueno, podrías tener un corte simple como sugerí, digamos 10 concatenaciones para una cadena de resultados, y es mejor que utilices StringBuilder. El umbral de corte podría aumentar deliberadamente, por lo que tiene la garantía de que generará beneficios de rendimiento. –

+0

@Wim Hollebrandse: Ver mi edición. :) –

2

Creo que sería un poco demasiado complejo para los escritores del compilador. Y cuando hace referencia a las cadenas intermedias dentro de los bucles además de la concatenación (por ejemplo, pasándolas a otros métodos más o menos), esta optimización no sería posible.

+8

¿Cuál de los redactores del compilador cree que sería demasiado complejo? Yo, Chris, Sam o Ian? –

+0

Tienes razón en eso. :) – treaschf

15

Para un solo concatenación de varias cadenas (por ejemplo, a + b + c + d + e + f + g + h + i + j) que realmente desea ser usando String.Concat OMI. Tiene la ventaja de crear una matriz para cada llamada, pero tiene la ventaja de que el método puede calcular la longitud exacta de la cadena resultante antes de que necesite asignar memoria. StringBuilder.Append(a).Append(b)... solo proporciona un valor único a la vez, por lo que el constructor no sabe cuánta memoria asignar.

En cuanto a hacerlo en bucles, en ese punto ha agregado una nueva variable local, y debe agregar código para volver a escribir la variable de cadena en el momento justo (llamando al StringBuilder.ToString()). ¿Qué sucede cuando estás ejecutando en el depurador? ¿No sería bastante confuso no ver el valor acumularse, solo hacerse visible al final del ciclo?Ah, y por supuesto debe realizar la validación adecuada de que el valor no se usa en ningún punto antes del final del ciclo ...

+7

El compilador de C# realmente compila 'a + b + c + d + e' a' string.Concat (a, b, c, d, e) '. De hecho, hay un error en la versión de 'Concat' con 4 parámetros que me ocasionaron algunos problemas: https://connect.microsoft.com/VisualStudio/feedback/details/361125/ –

+0

Mi punto sobre el uso de la cadena.Concat era precisamente por esa traducción, pero no estaba al tanto del error ... –

+0

@Downvoter: ¿me gustaría comentar? –

0

Probablemente porque es complicado hacer coincidir un patrón así en el código, y en caso de que el compilador no pueda hacer la combinación por alguna razón, el rendimiento del código es repentinamente terrible. La optimización de este tipo de código alentaría la escritura de códigos como ese, lo que aumentaría aún más el impacto negativo en los casos en que el compilador ya no pueda hacer la optimización.

Para concatenar un conjunto conocido de cadenas, StringBuilder no es más rápido que String.Concat.

-2

Una cadena es un tipo inmutable, por lo tanto, al concatenar la cadena es más lenta que con StringBuilder.Append.

Editar: Para aclarar mi punto un poco más, cuando se habla de por qué se String.Concat no optimizado para StringBuilder.Append, una clase StringBuilder tiene completamente diferentes a la semántica del tipo inmutable de String. ¿Por qué debería esperar que el compilador optimice eso ya que son claramente dos cosas diferentes? Además, un StringBuilder es un tipo mutable que puede cambiar su longitud de forma dinámica, ¿por qué debería un compilador optimizar un tipo inmutable a un tipo mutable? Ese es el diseño y la semántica incrustados en las especificaciones de ECMA para .NET Framework, independientemente del idioma.

Es un poco como preguntar el compilador (y tal vez esperar demasiado) para compilar un char y optimizarlo en un intint porque los trabajos de 32 bits en lugar de 8 bits y que serían consideradas más rápido!

+0

-1, soy claramente consciente de eso, y eso no responde la pregunta. –

+0

-1: También es incorrecto. 'String.Concaternate' asignará una sola salida de la longitud justa para todas las cadenas, y luego copiará una cada vez. Esto es más rápido que las llamadas múltiples a 'StringBuilder.Append' con múltiples asignaciones y copias. – Richard

+0

@Richard: Depende del tamaño y la frecuencia; si es para cadenas cortas, entonces sí, su afirmación sería cierta. Pero si estás hablando de cadenas largas, ¡es al revés, es decir, sería más lento! – t0mm13b

2

Porque el trabajo del compilador es generar código semánticamente correcto. Cambiar las invocaciones de String.Concat a las invocaciones de StringBuilder.Append estaría cambiando la semántica del código.

+0

Mientras la cadena resultante sea correcta, la semántica no se verá afectada; string.concat no tiene efectos secundarios observables. –

+1

@Eric Lippert: Sí, el efecto es el mismo, pero el IL emitido es diferente. La invocación de 'String.Concat' estaría de acuerdo con la especificación relativa a la invocación del miembro de la función, pero no lo reemplazaría con una llamada a' StringBuilder.Append'. Lo que quiero decir es "Le dije al compilador que invocara algún método y según la especificación espero que emita código para invocar ese método; espero que no invoque otro método". ¿Puede el compilador realmente hacer eso? Sé que en algunos casos optimizará y emitirá código semánticamente correcto pero diferente, pero me sorprende saber que es legal para las invocaciones de métodos. – jason

+0

Ah, entiendo tu punto. Si decidimos hacer esta optimización, por supuesto tendríamos que anotarla en la especificación, ya que la especificación dice que + en cadenas es un azúcar sintáctico para string.concat. –

30

La respuesta de LBuskin es excelente; Solo tengo un par de cosas para agregar.

En primer lugar, JScript.NET realiza esta optimización. JScript es utilizado con frecuencia por programadores menos experimentados para tareas que implican la construcción de grandes cadenas en bucles, como la construcción de objetos JSON, datos HTML, etc.

Dado que los programadores pueden no conocer el costo n-cuadrado de la asignación ingeniosa de cadenas, pueden no ser conscientes de la existencia de constructores de cadenas y escribir código con frecuencia usando este patrón, sentimos que era razonable poner esto optimización en JScript.NET.

Los programadores de C# tienden a ser más conscientes de los costos subyacentes del código que escriben y más conscientes de la existencia de piezas comerciales como StringBuilder, por lo que necesitan menos esta optimización. Y más fundamentalmente, la filosofía de diseño de C# es que es un lenguaje de "haz lo que dije" con un mínimo de "magia"; JScript es un lenguaje de "haga lo que quiero decir" que hace todo lo posible para descubrir cómo servirle mejor, incluso si eso significa a veces adivinar lo incorrecto. Ambas filosofías son válidas y útiles.

A veces "va por el otro lado". Compare esta elección con la elección que hacemos para los interruptores en las cadenas. Los conmutadores de cadenas se compilan realmente como una creación de un diccionario que contiene las cadenas, en lugar de como una serie de comparaciones de cadenas. Esa optimización podría ser mala; podría ser más rápido simplemente hacer las comparaciones de cadenas. Pero aquí adivinamos que "quiso decir" que el cambio era una búsqueda de tabla en lugar de una serie de declaraciones "si": si se refería a la serie de enunciados if, podría escribirlo usted mismo fácilmente.

+0

¿Pero por qué Java hace esto? – x2bool

+0

Además de las concatenaciones que no se convierten automáticamente en llamadas 'StringBuilder', ¿es este el motivo por el cual todo el proceso en general (incluida la decisión de usar un' StringBuilder' o no) no se hace automáticamente y se oculta por el compilador ? – Panzercrisis

Cuestiones relacionadas