Tenemos que construir cadenas todo el tiempo para la salida del registro, etc. Sobre las versiones JDK hemos aprendido cuándo usar StringBuffer
(muchos apéndices, seguros para subprocesos) y StringBuilder
(muchos apéndices, no seguros para subprocesos).
¿Cuál es el consejo sobre el uso String.format()
? ¿Es eficiente o nos vemos obligados a mantener la concatenación de líneas simples donde el rendimiento es importante?
por ejemplo, feo estilo antiguo,
String s = "What do you get if you multiply " + varSix + " by " + varNine + "?";
vs. nuevo estilo ordenado (String.format, que posiblemente sea más lento),
String s = String.format("What do you get if you multiply %d by %d?", varSix, varNine);
Nota: mi caso de uso específico son los cientos de cadenas de registro de 'una línea' en todo mi código. No implican un bucle, por lo que StringBuilder
es demasiado pesado. Estoy interesado String.format()
específicamente.
Respuestas:
Escribí una clase pequeña para probar que tiene el mejor rendimiento de los dos y + viene antes del formato. por un factor de 5 a 6. Pruébelo usted mismo
Ejecutar lo anterior para diferentes N muestra que ambos se comportan linealmente, pero
String.format
es 5-30 veces más lento.La razón es que en la implementación actual
String.format
primero analiza la entrada con expresiones regulares y luego completa los parámetros. La concatenación con más, por otro lado, se optimiza con javac (no con el JIT) y se usaStringBuilder.append
directamente.fuente
Tomé hhafez código y añadido una prueba de memoria :
Ejecuto esto por separado para cada enfoque, el operador '+', String.format y StringBuilder (llamando a ToString ()), por lo que la memoria utilizada no se verá afectada por otros enfoques. Agregué más concatenaciones, haciendo que la cadena sea "Blah" + i + "Blah" + i + "Blah" + i + "Blah".
Los resultados son los siguientes (promedio de 5 ejecuciones cada uno):
Tiempo de aproximación (ms) Memoria asignada (larga)
Operador '+' 747 320,504
String.format 16484 373,312
StringBuilder 769 57,344
Podemos ver que String '+' y StringBuilder son prácticamente idénticos en el tiempo, pero StringBuilder es mucho más eficiente en el uso de la memoria. Esto es muy importante cuando tenemos muchas llamadas de registro (o cualquier otra declaración que involucre cadenas) en un intervalo de tiempo lo suficientemente corto como para que el recolector de basura no pueda limpiar las muchas instancias de cadenas que resultan del operador '+'.
Y una nota, por cierto, no olvide verificar el nivel de registro antes de construir el mensaje.
Conclusiones:
fuente
+
operador compila alStringBuilder
código equivalente . Los microbenchmarks como este no son una buena forma de medir el rendimiento; por qué no usar jvisualvm, está en el jdk por una razón.String.format()
será más lento, pero debido al tiempo para analizar la cadena de formato en lugar de las asignaciones de objetos. Aplazar la creación de artefactos de registro hasta que esté seguro de que son necesarios es un buen consejo, pero si tendría un impacto en el rendimiento, está en el lugar equivocado.And a note, BTW, don't forget to check the logging level before constructing the message.
No es un buen consejo. Suponiendo que estamos hablandojava.util.logging.*
específicamente, verificar el nivel de registro es cuando se habla de hacer un procesamiento avanzado que causaría efectos adversos en un programa que no desearía cuando un programa no tiene el registro activado en el nivel apropiado. El formato de cadena no es ese tipo de procesamiento en absoluto. El formateo es parte deljava.util.logging
marco, y el registrador mismo verifica el nivel de registro antes de que se invoque el formateador.Todos los puntos de referencia presentados aquí tienen algunos defectos , por lo que los resultados no son confiables.
Me sorprendió que nadie usara JMH para la evaluación comparativa, así que lo hice.
Resultados:
Las unidades son operaciones por segundo, cuanto más, mejor. Código fuente de referencia . Se utilizó la máquina virtual Java OpenJDK IcedTea 2.5.4.
Entonces, el estilo antiguo (usando +) es mucho más rápido.
fuente
JAVAC 1.6 compila automáticamente su viejo estilo feo como:
Por lo tanto, no hay absolutamente ninguna diferencia entre esto y usar un StringBuilder.
String.format es mucho más pesado ya que crea un nuevo formateador, analiza su cadena de formato de entrada, crea un StringBuilder, lo agrega todo y llama aString ().
fuente
+
y deStringBuilder
hecho. Lamentablemente, hay mucha información errónea en otras respuestas en este hilo. Estoy casi tentado a cambiar la pregunta ahow should I not be measuring performance
.El String.format de Java funciona así:
Si el destino final de estos datos es una secuencia (por ejemplo, renderizar una página web o escribir en un archivo), puede ensamblar los fragmentos de formato directamente en su secuencia:
Especulo que el optimizador optimizará el procesamiento de cadenas de formato. Si es así, tiene un rendimiento amortizado equivalente para desenrollar manualmente su String.format en un StringBuilder.
fuente
String.format
en bucles internos (que se ejecuta millones de veces) resultó en más del 10% de mi tiempo de ejecución invertidojava.util.Formatter.parse(String)
. Esto parece indicar que en los bucles internos, debe evitar llamarFormatter.format
o cualquier cosa que lo llame, incluyendoPrintStream.format
(una falla en la lib estándar de Java, IMO, especialmente porque no puede almacenar en caché la cadena de formato analizada).Para expandir / corregir la primera respuesta anterior, no es la traducción con la que String.format ayudaría, en realidad.
Lo que String.format ayudará será cuando imprima una fecha / hora (o un formato numérico, etc.), donde haya diferencias de localización (l10n) (es decir, algunos países imprimirán 04Feb2009 y otros imprimirán Feb042009).
Con la traducción, solo está hablando de mover cualquier cadena externa (como mensajes de error y demás) a un paquete de propiedades para que pueda usar el paquete correcto para el idioma correcto, utilizando ResourceBundle y MessageFormat.
Mirando todo lo anterior, diría que, en cuanto al rendimiento, String.format vs.concatenación simple se reduce a lo que prefieres. Si prefiere mirar las llamadas a .format sobre la concatenación, entonces, por supuesto, vaya con eso.
Después de todo, el código se lee mucho más de lo que se escribe.
fuente
En su ejemplo, el rendimiento probalby no es muy diferente, pero hay otros problemas a considerar: la fragmentación de la memoria. Incluso la operación de concatenación está creando una nueva cadena, incluso si es temporal (lleva tiempo GC y es más trabajo). String.format () es simplemente más legible e implica menos fragmentación.
Además, si usa mucho un formato particular, no olvide que puede usar la clase Formatter () directamente (todo lo que String.format () hace es instanciar una instancia de Formatter de un solo uso).
Además, debe tener en cuenta algo más: tenga cuidado de usar substring (). Por ejemplo:
Esa cadena grande todavía está en la memoria porque así es como funcionan las subcadenas de Java. Una mejor versión es:
o
La segunda forma es probablemente más útil si está haciendo otras cosas al mismo tiempo.
fuente
En general, debe usar String.Format porque es relativamente rápido y admite la globalización (suponiendo que realmente esté tratando de escribir algo que lea el usuario). También facilita la globalización si está tratando de traducir una cadena versus 3 o más por declaración (especialmente para lenguajes que tienen estructuras gramaticales drásticamente diferentes).
Ahora, si nunca planea traducir nada, confíe en la conversión incorporada de Java de los operadores +
StringBuilder
. O use JavaStringBuilder
explícitamente.fuente
Otra perspectiva desde el punto de vista de registro solamente.
Veo mucha discusión relacionada con el inicio de sesión en este hilo, así que pensé en agregar mi experiencia en respuesta. Puede ser que alguien lo encuentre útil.
Supongo que la motivación de iniciar sesión con formateador proviene de evitar la concatenación de cadenas. Básicamente, no desea tener una sobrecarga de string concat si no va a registrarlo.
Realmente no necesita concat / formato a menos que desee iniciar sesión. Digamos si defino un método como este
En este enfoque, el cancat / formateador no se llama realmente si es un mensaje de depuración y debugOn = false
Aunque aún será mejor usar StringBuilder en lugar de formateador aquí. La principal motivación es evitar todo eso.
Al mismo tiempo, no me gusta agregar el bloque "if" para cada instrucción de registro desde
Por lo tanto, prefiero crear una clase de utilidad de registro con métodos como el anterior y usarla en todas partes sin preocuparme por el impacto en el rendimiento y cualquier otro problema relacionado con él.
fuente
Acabo de modificar la prueba de hhafez para incluir StringBuilder. StringBuilder es 33 veces más rápido que String.format con el cliente jdk 1.6.0_10 en XP. El uso del interruptor -server reduce el factor a 20.
Si bien esto puede sonar drástico, considero que es relevante solo en casos raros, porque los números absolutos son bastante bajos: 4 s por 1 millón de llamadas simples de String.format es algo correcto, siempre que los use para iniciar sesión o me gusta.
Actualización: como lo señaló sjbotha en los comentarios, la prueba StringBuilder no es válida, ya que falta una final
.toString()
.El factor de aceleración correcto de
String.format(.)
aStringBuilder
es 23 en mi máquina (16 con el-server
interruptor).fuente
Aquí está la versión modificada de la entrada hhafez. Incluye una opción de generador de cadenas.
}
Tiempo posterior para el ciclo 391 Tiempo posterior para el ciclo 4163 Tiempo posterior para el ciclo 227
fuente
La respuesta a esto depende mucho de cómo su compilador Java específico optimiza el código de bytes que genera. Las cadenas son inmutables y, en teoría, cada operación "+" puede crear una nueva. Pero, su compilador seguramente optimiza los pasos intermedios en la construcción de cadenas largas. Es completamente posible que ambas líneas de código anteriores generen exactamente el mismo bytecode.
La única forma real de saber es probar el código de forma iterativa en su entorno actual. Escriba una aplicación QD que concatene cadenas de forma iterativa y vea cómo se agota el tiempo una contra la otra.
fuente
Considere usar
"hello".concat( "world!" )
para un pequeño número de cadenas en concatenación. Podría ser incluso mejor para el rendimiento que otros enfoques.Si tiene más de 3 cadenas, considere usar StringBuilder, o simplemente String, dependiendo del compilador que use.
fuente