java string performance string-formatting micro-optimization

string.format java



¿Debo usar String.format() de Java si el rendimiento es importante? (13)

Acabo de modificar la prueba de hhafez para incluir StringBuilder. StringBuilder es 33 veces más rápido que String.format utilizando el cliente jdk 1.6.0_10 en XP. El uso del conmutador -servidor reduce el factor a 20.

public class StringTest { public static void main( String[] args ) { test(); test(); } private static void test() { int i = 0; long prev_time = System.currentTimeMillis(); long time; for ( i = 0; i < 1000000; i++ ) { String s = "Blah" + i + "Blah"; } time = System.currentTimeMillis() - prev_time; System.out.println("Time after for loop " + time); prev_time = System.currentTimeMillis(); for ( i = 0; i < 1000000; i++ ) { String s = String.format("Blah %d Blah", i); } time = System.currentTimeMillis() - prev_time; System.out.println("Time after for loop " + time); prev_time = System.currentTimeMillis(); for ( i = 0; i < 1000000; i++ ) { new StringBuilder("Blah").append(i).append("Blah"); } time = System.currentTimeMillis() - prev_time; System.out.println("Time after for loop " + time); } }

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 para 1 millón de llamadas String.format simples están bien, siempre y cuando las use para el registro o el me gusta.

Actualización: Como lo señaló sjbotha en los comentarios, la prueba de StringBuilder no es válida, ya que falta un .toString() final.

El factor de String.format(.) de String.format(.) StringBuilder es 23 en mi máquina (16 con el interruptor -server ).

Tenemos que construir cadenas todo el tiempo para la salida del registro y así sucesivamente. En las versiones de JDK, hemos aprendido cuándo usar StringBuffer (muchos apéndices, seguro para subprocesos) y StringBuilder (muchos apéndices, no seguros para subprocesos).

¿Cuál es el consejo sobre el uso de String.format() ? ¿Es eficiente, o nos vemos obligados a seguir con la concatenación de líneas individuales donde el rendimiento es importante?

por ejemplo, el viejo estilo feo,

String s = "What do you get if you multiply " + varSix + " by " + varNine + "?");

vs nuevo estilo ordenado (y posiblemente 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 sola línea" en todo mi código. No implican un bucle, por lo que StringBuilder es demasiado pesado. Estoy interesado en String.format() específicamente.


Aquí está la versión modificada de la entrada hhafez. Incluye una opción de generador de cadenas.

public class BLA { public static final String BLAH = "Blah "; public static final String BLAH2 = " Blah"; public static final String BLAH3 = "Blah %d Blah"; public static void main(String[] args) { int i = 0; long prev_time = System.currentTimeMillis(); long time; int numLoops = 1000000; for( i = 0; i< numLoops; i++){ String s = BLAH + i + BLAH2; } time = System.currentTimeMillis() - prev_time; System.out.println("Time after for loop " + time); prev_time = System.currentTimeMillis(); for( i = 0; i<numLoops; i++){ String s = String.format(BLAH3, i); } time = System.currentTimeMillis() - prev_time; System.out.println("Time after for loop " + time); prev_time = System.currentTimeMillis(); for( i = 0; i<numLoops; i++){ StringBuilder sb = new StringBuilder(); sb.append(BLAH); sb.append(i); sb.append(BLAH2); String s = sb.toString(); } time = System.currentTimeMillis() - prev_time; System.out.println("Time after for loop " + time); }

}

Tiempo después para for loop 391 Tiempo después para for loop 4163 Tiempo después para for loop 227


Considere el uso de "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 utilizar StringBuilder, o simplemente String, dependiendo del compilador que utilice.


El String.format de Java funciona así:

  1. Analiza la cadena de formato, explotando en una lista de fragmentos de formato.
  2. itera los fragmentos de formato, representándose en un StringBuilder, que es básicamente una matriz que se redimensiona a sí misma según sea necesario, al copiarla en una nueva matriz. esto es necesario porque todavía no sabemos qué tan grande es para asignar la Cadena final
  3. StringBuilder.toString () copia su búfer interno en una nueva cadena

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:

new PrintStream(outputStream, autoFlush, encoding).format("hello {0}", "world");

Especulo que el optimizador optimizará el procesamiento de cadenas de formato. Si es así, le queda un rendimiento amortized equivalente para desenrollar manualmente su String.format en un StringBuilder.


En su ejemplo, el rendimiento probalby no es muy diferente, pero hay otros aspectos a considerar: la fragmentación de la memoria. Incluso la operación de concatenar está creando una nueva cadena, incluso si es temporal (se necesita tiempo para GC y es más trabajo). String.format () es más legible e implica menos fragmentación.

Además, si usa mucho un formato en particular, no olvide que puede usar la clase Formatter () directamente (todo lo que hace String.format () es instanciar una instancia de Formatter de un solo uso).

Además, debe tener en cuenta algo más: tenga cuidado al utilizar subcadena (). Por ejemplo:

String getSmallString() { String largeString = // load from file; say 2M in size return largeString.substring(100, 300); }

Esa cadena grande aún está en la memoria porque así es como funcionan las subcadenas de Java. Una mejor versión es:

return new String(largeString.substring(100, 300));

o

return String.format("%s", largeString.substring(100, 300));

La segunda forma es probablemente más útil si estás haciendo otras cosas al mismo tiempo.


Escribí una clase pequeña para probar que tiene el mejor rendimiento de los dos y + viene por delante del formato. por un factor de 5 a 6. Pruébelo usted mismo

import java.io.*; import java.util.Date; public class StringTest{ public static void main( String[] args ){ int i = 0; long prev_time = System.currentTimeMillis(); long time; for( i = 0; i< 100000; i++){ String s = "Blah" + i + "Blah"; } time = System.currentTimeMillis() - prev_time; System.out.println("Time after for loop " + time); prev_time = System.currentTimeMillis(); for( i = 0; i<100000; i++){ String s = String.format("Blah %d Blah", i); } time = System.currentTimeMillis() - prev_time; System.out.println("Time after for loop " + time); } }

Ejecutar lo anterior para diferentes N muestra que ambos se comportan linealmente, pero el String.format es 5 a 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 plus, por otro lado, se optimiza con javac (no con el JIT) y usa StringBuilder.append directamente.


La respuesta a esto depende en gran medida de cómo su compilador Java específico optimiza el código de byte que genera. Las cadenas son inmutables y, teóricamente, cada operación "+" puede crear una nueva. Pero, su compilador casi con seguridad optimiza los pasos intermedios en la construcción de cadenas largas. Es totalmente posible que las dos líneas de código anteriores generen exactamente el mismo código de bytes.

La única forma real de saberlo 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 contraponen entre sí.


Para expandir / corregir en la primera respuesta anterior, no es la traducción con la que String.format ayudaría, en realidad.
Con qué String.format le ayudará es cuando está imprimiendo una fecha / hora (o un formato numérico, etc.), donde hay 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 externalizable (como mensajes de error y lo que no) a un paquete de propiedades para que pueda usar el paquete correcto para el idioma correcto, usando ResourceBundle y MessageFormat.

Mirando todo lo anterior, diría que en cuanto a rendimiento, el formato String.format vs. la concatenación simple se reduce a lo que usted prefiere. Si prefiere ver las llamadas a .format en lugar de la concatenación, entonces con todo, vaya con eso.
Después de todo, el código se lee mucho más de lo que está escrito.


Por lo general, debe usar String.Format porque es relativamente rápido y es compatible con la globalización (asumiendo que en realidad está intentando escribir algo que el usuario lee). También facilita la globalización si intenta traducir una cadena en lugar de 3 o más por instrucción (especialmente para idiomas que tienen estructuras gramaticales drásticamente diferentes).

Ahora, si nunca piensa traducir nada, entonces confíe en la conversión integrada de operadores + de Java a StringBuilder . O use StringBuilder de Java explícitamente.


Todos los puntos de referencia presentados aquí tienen algunos flaws , por lo que los resultados no son confiables.

Me sorprendió que nadie utilizara JMH para realizar evaluaciones comparativas, así que lo hice.

Resultados:

Benchmark Mode Cnt Score Error Units MyBenchmark.testOld thrpt 20 9645.834 ± 238.165 ops/s // using + MyBenchmark.testNew thrpt 20 429.898 ± 10.551 ops/s // using String.format

Las unidades son operaciones por segundo, cuanto más mejor. Código fuente de referencia . Se utilizó OpenJDK IcedTea 2.5.4 Java Virtual Machine.

Entonces, el estilo antiguo (usando +) es mucho más rápido.


Tu viejo estilo feo es compilado automáticamente por JAVAC 1.6 como:

StringBuilder sb = new StringBuilder("What do you get if you multiply "); sb.append(varSix); sb.append(" by "); sb.append(varNine); sb.append("?"); String s = sb.toString();

Así que no hay absolutamente ninguna diferencia entre esto y usar un StringBuilder.

String.format es mucho más pesado, ya que crea un nuevo Formatter, analiza la cadena de formato de entrada, crea un StringBuilder, lo agrega todo y llama aString ().


Otra perspectiva solo desde el punto de vista de registro.

Veo mucha discusión relacionada con el registro en este hilo, así que pensé en agregar mi experiencia como respuesta. Puede ser que alguien lo encuentre útil.

Supongo que la motivación del registro con el formateador proviene de evitar la concatenación de cadenas. Básicamente, no desea tener una sobrecarga de concat de cadena si no va a registrarla.

Realmente no necesita concat / format a menos que desee iniciar sesión. Digamos si defino un método como este.

public void logDebug(String... args, Throwable t) { if(debugOn) { // call concat methods for all args //log the final debug message } }

En este enfoque, el cancat / formatter no se llama realmente si es un mensaje de depuración y debugOn = false

Aunque todavía será mejor usar StringBuilder en lugar del formateador aquí. La principal motivación es evitar todo eso.

Al mismo tiempo, no me gusta agregar el bloque "if" para cada declaración de registro ya que

  • Afecta la legibilidad
  • Reduce la cobertura en mis pruebas de unidad, eso es confuso cuando quiere asegurarse de que se prueban todas las líneas.

Por lo tanto, prefiero crear una clase de utilidad de registro con métodos como el anterior y usarlo en todas partes sin preocuparme por el impacto en el rendimiento y cualquier otro problema relacionado con él.


hhafez código de hhafez y agregué una prueba de memoria :

private static void test() { Runtime runtime = Runtime.getRuntime(); long memory; ... memory = runtime.freeMemory(); // for loop code memory = memory-runtime.freeMemory();

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 la cadena como "Blah" + i + "Blah" + i + "Blah" + i + "Blah".

Los resultados son los siguientes (promedio de 5 carreras 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 suficientemente corto para que el recolector de basura no pueda limpiar las muchas instancias de cadena que resultan del operador ''+''.

Y una nota, por cierto, no olvide verificar el nivel de registro antes de construir el mensaje.

Conclusiones:

  1. Seguiré usando StringBuilder.
  2. Tengo demasiado tiempo o muy poca vida.