Tengo una pregunta relacionada con el rendimiento sobre el uso de StringBuilder. En un bucle muy largo, estoy manipulando un StringBuilder
y pasándolo a otro método como este:
for (loop condition) {
StringBuilder sb = new StringBuilder();
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
¿La instanciación StringBuilder
en cada ciclo de bucle es una buena solución? ¿Y es mejor llamar a una eliminación, como la siguiente?
StringBuilder sb = new StringBuilder();
for (loop condition) {
sb.delete(0, sb.length);
sb.append("some string");
. . .
sb.append(anotherString);
. . .
passToMethod(sb.toString());
}
java
performance
string
stringbuilder
Pier Luigi
fuente
fuente
En la filosofía de escribir código sólido, siempre es mejor poner tu StringBuilder dentro de tu bucle. De esta manera no sale del código para el que está destinado.
En segundo lugar, la mayor mejora en StringBuilder proviene de darle un tamaño inicial para evitar que crezca mientras se ejecuta el ciclo.
fuente
Más rápido todavía:
En la filosofía de escribir código sólido, el funcionamiento interno del método debe estar oculto a los objetos que usan el método. Por lo tanto, desde la perspectiva del sistema, no hay diferencia si vuelve a declarar el StringBuilder dentro del bucle o fuera del bucle. Dado que declararlo fuera del ciclo es más rápido y no hace que el código sea más complicado de leer, reutilice el objeto en lugar de reinstalarlo.
Incluso si el código era más complicado y sabía con certeza que la instanciación de objetos era el cuello de botella, coméntelo.
Tres carreras con esta respuesta:
Tres carreras con la otra respuesta:
Aunque no es significativo, configurar el
StringBuilder
tamaño de búfer inicial de la le dará una pequeña ganancia.fuente
Bien, ahora entiendo lo que está pasando y tiene sentido.
Tenía la impresión de que
toString
acababa de pasar el subyacentechar[]
a un constructor de cadenas que no tomó una copia. Entonces se haría una copia en la siguiente operación de "escritura" (por ejemplodelete
). Creo que este fue el casoStringBuffer
de alguna versión anterior. (No lo es ahora). Pero no,toString
simplemente pasa la matriz (y el índice y la longitud) alString
constructor público que toma una copia.Entonces, en el caso de "reutilizar el
StringBuilder
", realmente creamos una copia de los datos por cadena, usando la misma matriz de caracteres en el búfer todo el tiempo. Obviamente, crear uno nuevoStringBuilder
cada vez crea un nuevo búfer subyacente, y luego ese búfer se copia (algo inútil, en nuestro caso particular, pero se hace por razones de seguridad) al crear una nueva cadena.Todo esto lleva a que la segunda versión sea definitivamente más eficiente, pero al mismo tiempo, diría que es un código más feo.
fuente
Como no creo que se haya señalado todavía, debido a las optimizaciones integradas en el compilador Sun Java, que crea automáticamente StringBuilders (StringBuffers pre-J2SE 5.0) cuando ve concatenaciones de cadenas, el primer ejemplo de la pregunta es equivalente a:
Cuál es más legible, en mi opinión, el mejor enfoque. Sus intentos de optimizar pueden resultar en ganancias en algunas plataformas, pero potencialmente en pérdidas en otras.
Pero si realmente tiene problemas con el rendimiento, entonces optimice. Sin embargo, comenzaría especificando explícitamente el tamaño del búfer del StringBuilder, según Jon Skeet.
fuente
La JVM moderna es realmente inteligente para cosas como esta. No lo adivinaría y haría algo hacky que sea menos fácil de mantener / legible ... a menos que haga puntos de referencia adecuados con datos de producción que validen una mejora de rendimiento no trivial (y la documente;)
fuente
Según mi experiencia con el desarrollo de software en Windows, diría que limpiar StringBuilder durante su ciclo tiene un mejor rendimiento que crear una instancia de StringBuilder con cada iteración. Borrarlo libera esa memoria para que se sobrescriba inmediatamente sin necesidad de asignación adicional. No estoy lo suficientemente familiarizado con el recolector de basura de Java, pero creo que liberar y no reasignar (a menos que su próxima cadena haga crecer StringBuilder) es más beneficioso que la instanciación.
(Mi opinión es contraria a lo que todos los demás están sugiriendo. Hmm. Es hora de compararlo).
fuente
La razón por la que hacer 'setLength' o 'delete' mejora el rendimiento es principalmente porque el código 'aprende' el tamaño correcto del búfer y menos para hacer la asignación de memoria. Generalmente, recomiendo dejar que el compilador haga las optimizaciones de cadenas . Sin embargo, si el rendimiento es crítico, a menudo calcularé previamente el tamaño esperado del búfer. El tamaño predeterminado de StringBuilder es de 16 caracteres. Si crece más allá de eso, entonces tiene que cambiar de tamaño. Cambiar el tamaño es donde se pierde el rendimiento. Aquí hay otro mini-benchmark que ilustra esto:
Los resultados muestran que reutilizar el objeto es aproximadamente un 10% más rápido que crear un búfer del tamaño esperado.
fuente
LOL, la primera vez que veo a personas comparan el rendimiento combinando cadenas en StringBuilder. Para ese propósito, si usa "+", podría ser incluso más rápido; D. El propósito de utilizar StringBuilder para acelerar la recuperación de toda la cadena como concepto de "localidad".
En el caso de que recupere un valor de cadena con frecuencia que no necesite cambios frecuentes, Stringbuilder permite un mayor rendimiento de la recuperación de cadenas. Y ese es el propósito de usar Stringbuilder ... por favor, no pruebe MIS el propósito principal de eso ...
Algunas personas dijeron: El avión vuela más rápido. Por lo tanto, lo probé con mi bicicleta y descubrí que el avión se mueve más lento. ¿Sabes cómo configuro la configuración del experimento? D
fuente
No significativamente más rápido, pero de mis pruebas muestra que, en promedio, es un par de milisegundos más rápido usando 1.6.0_45 64 bits: use StringBuilder.setLength (0) en lugar de StringBuilder.delete ():
fuente
La forma más rápida es utilizar "setLength". No implicará la operación de copia. La forma de crear un nuevo StringBuilder debería estar completamente descartada . La lentitud para StringBuilder.delete (int start, int end) es porque copiará la matriz nuevamente para la parte de cambio de tamaño.
Después de eso, StringBuilder.delete () actualizará StringBuilder.count al nuevo tamaño. Mientras que StringBuilder.setLength () simplemente simplifica, actualice StringBuilder.count al nuevo tamaño.
fuente
El primero es mejor para los humanos. Si el segundo es un poco más rápido en algunas versiones de algunas JVM, ¿entonces qué?
Si el rendimiento es tan crítico, omita StringBuilder y escriba el suyo. Si eres un buen programador y tienes en cuenta cómo tu aplicación utiliza esta función, deberías poder hacerlo aún más rápido. ¿Vale la pena? Probablemente no.
¿Por qué esta pregunta se considera "pregunta favorita"? Porque la optimización del rendimiento es muy divertida, no importa si es práctica o no.
fuente
No creo que tenga sentido tratar de optimizar el rendimiento de esa manera. Hoy (2019) las dos declaraciones se ejecutan durante aproximadamente 11 segundos para 100.000.000 de bucles en mi computadora portátil I5:
==> 11000 mseg (declaración dentro del bucle) y 8236 mseg (declaración fuera del bucle)
Incluso si estoy ejecutando programas para la deduplicación de direcciones con mil millones de bucles, una diferencia de 2 segundos. para 100 millones de bucles no hace ninguna diferencia porque los programas se ejecutan durante horas. También tenga en cuenta que las cosas son diferentes si solo tiene una declaración adjunta:
==> 3416 mseg (bucle interior), 3555 mseg (bucle exterior) La primera declaración que está creando el StringBuilder dentro del bucle es más rápida en ese caso. Y, si cambia el orden de ejecución, es mucho más rápido:
==> 3638 mseg (bucle exterior), 2908 mseg (bucle interior)
Saludos, Ulrich
fuente
Declare una vez y asigne cada vez. Es un concepto más pragmático y reutilizable que una optimización.
fuente