Hay dos 'fuerzas' aquí, en tensión: Rendimiento vs. Legibilidad.
Sin embargo, abordemos primero el tercer problema, líneas largas:
System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");
La mejor manera de implementar esto y mantener la lectura es utilizar la concatenación de cadenas:
System.out.println("Good morning everyone. I am here today to present you "
+ "with a very, very lengthy sentence in order to prove a "
+ "point about how it looks strange amongst other code.");
La concatenación de cadena constante ocurrirá en el momento de la compilación y no tendrá ningún efecto en el rendimiento. Las líneas son legibles y puedes seguir adelante.
Ahora, sobre el:
System.out.println("Good morning.");
System.out.println("Please enter your name");
vs.
System.out.println("Good morning.\nPlease enter your name");
La segunda opción es significativamente más rápida. Sugeriré alrededor de 2X tan rápido ... ¿por qué?
Debido a que el 90% (con un amplio margen de error) del trabajo no está relacionado con el volcado de los caracteres a la salida, sino que es una sobrecarga necesaria para asegurar la salida para escribir en ella.
Sincronización
System.out
es un PrintStream
. Todas las implementaciones de Java que conozco, sincronizan internamente el PrintStream: ¡ Vea el código en GrepCode! .
¿Qué significa esto para tu código?
Significa que cada vez que llama System.out.println(...)
está sincronizando su modelo de memoria, está verificando y esperando un bloqueo. Cualquier otro hilo que llame a System.out también estará bloqueado.
En las aplicaciones de un solo subproceso, el impacto de a System.out.println()
menudo está limitado por el rendimiento de E / S de su sistema, qué tan rápido puede escribir en un archivo. En aplicaciones multiproceso, el bloqueo puede ser más problemático que el IO.
Enrojecimiento
Cada impresión se enjuaga . Esto hace que los búferes se borren y desencadena una escritura de nivel de consola en los búferes. La cantidad de esfuerzo realizada aquí depende de la implementación, pero, en general, se entiende que el rendimiento del vaciado solo está relacionado en una pequeña parte con el tamaño del búfer que se está vaciando. Hay una sobrecarga significativa relacionada con el vaciado, donde los búferes de memoria están marcados como sucios, la máquina virtual está realizando E / S, y así sucesivamente. Incurrir esa sobrecarga una vez, en lugar de dos, es una optimización obvia.
Algunos numeros
Realicé la siguiente pequeña prueba:
public class ConsolePerf {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
benchmark("Warm " + i);
}
benchmark("real");
}
private static void benchmark(String string) {
benchString(string + "short", "This is a short String");
benchString(string + "long", "This is a long String with a number of newlines\n"
+ "in it, that should simulate\n"
+ "printing some long sentences and log\n"
+ "messages.");
}
private static final int REPS = 1000;
private static void benchString(String name, String value) {
long time = System.nanoTime();
for (int i = 0; i < REPS; i++) {
System.out.println(value);
}
double ms = (System.nanoTime() - time) / 1000000.0;
System.err.printf("%s run in%n %12.3fms%n %12.3f lines per ms%n %12.3f chars per ms%n",
name, ms, REPS/ms, REPS * (value.length() + 1) / ms);
}
}
El código es relativamente simple, imprime repetidamente una cadena corta o larga para generar. La cadena larga tiene múltiples líneas nuevas. Mide cuánto tiempo lleva imprimir 1000 iteraciones de cada una.
Si lo ejecuto en el símbolo del sistema de Unix (Linux), y redirijo el STDOUT
a /dev/null
e imprimo los resultados reales STDERR
, puedo hacer lo siguiente:
java -cp . ConsolePerf > /dev/null 2> ../errlog
La salida (en errlog) se ve así:
Warm 0short run in
7.264ms
137.667 lines per ms
3166.345 chars per ms
Warm 0long run in
1.661ms
602.051 lines per ms
74654.317 chars per ms
Warm 1short run in
1.615ms
619.327 lines per ms
14244.511 chars per ms
Warm 1long run in
2.524ms
396.238 lines per ms
49133.487 chars per ms
.......
Warm 99short run in
1.159ms
862.569 lines per ms
19839.079 chars per ms
Warm 99long run in
1.213ms
824.393 lines per ms
102224.706 chars per ms
realshort run in
1.204ms
830.520 lines per ms
19101.959 chars per ms
reallong run in
1.215ms
823.160 lines per ms
102071.811 chars per ms
¿Qué significa esto? Permítanme repetir la última 'estrofa':
realshort run in
1.204ms
830.520 lines per ms
19101.959 chars per ms
reallong run in
1.215ms
823.160 lines per ms
102071.811 chars per ms
Significa que, para todos los efectos, aunque la línea 'larga' es aproximadamente 5 veces más larga y contiene varias líneas nuevas, la salida corta tarda casi tanto como la línea corta.
El número de caracteres por segundo a largo plazo es 5 veces mayor, y el tiempo transcurrido es casi el mismo .....
En otras palabras, su rendimiento escala en relación con el número de impresiones que tiene, no con lo que imprimen.
Actualización: ¿Qué sucede si redirige a un archivo, en lugar de a / dev / null?
realshort run in
2.592ms
385.815 lines per ms
8873.755 chars per ms
reallong run in
2.686ms
372.306 lines per ms
46165.955 chars per ms
Es mucho más lento, pero las proporciones son casi las mismas ...