Sé que la salida a la consola es una operación costosa. En aras de la legibilidad del código, a veces es bueno llamar a una función para generar texto dos veces, en lugar de tener una larga cadena de texto como argumento.
Por ejemplo, ¿cuánto menos eficiente es tener
System.out.println("Good morning.");
System.out.println("Please enter your name");
vs.
System.out.println("Good morning.\nPlease enter your name");
En el ejemplo, la diferencia es solo una llamada, println()
pero ¿y si es más?
En una nota relacionada, las declaraciones relacionadas con la impresión de texto pueden parecer extrañas mientras se visualiza el código fuente si el texto a imprimir es largo. Suponiendo que el texto en sí no se puede acortar, ¿qué se puede hacer? ¿Debería ser este un caso en el que println()
se realicen múltiples llamadas? Una vez alguien me dijo que una línea de código no debería tener más de 80 caracteres (IIRC), entonces, ¿qué harías con
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.");
¿Es lo mismo para lenguajes como C / C ++ ya que cada vez que los datos se escriben en un flujo de salida se debe realizar una llamada al sistema y el proceso debe pasar al modo kernel (que es muy costoso)?
Respuestas:
Hay dos 'fuerzas' aquí, en tensión: Rendimiento vs. Legibilidad.
Sin embargo, abordemos primero el tercer problema, líneas largas:
La mejor manera de implementar esto y mantener la lectura es utilizar la concatenación de cadenas:
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:
vs.
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 unPrintStream
. 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:
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 realesSTDERR
, puedo hacer lo siguiente:La salida (en errlog) se ve así:
¿Qué significa esto? Permítanme repetir la última 'estrofa':
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?
Es mucho más lento, pero las proporciones son casi las mismas ...
fuente
"\n"
puede no ser el terminador de línea correcto.println
automáticamente terminará la línea con los caracteres correctos, pero pegar un\n
a su cadena directamente puede causar problemas. Si desea hacerlo bien, es posible que deba usar el formato de cadena o laline.separator
propiedad del sistema .println
Es mucho más limpio.No creo que tener un montón de
println
s sea un problema de diseño en absoluto. La forma en que lo veo es que esto se puede hacer claramente con el analizador de código estático si realmente es un problema.Pero no es un problema porque la mayoría de las personas no hacen IOs como este. Cuando realmente necesitan hacer muchas E / S, usan las almacenadas en búfer (BufferedReader, BufferedWriter, etc.) cuando la entrada está almacenada, verá que el rendimiento es lo suficientemente similar, que no necesita preocuparse por tener un manojo de
println
o pocosprintln
.Entonces para responder la pregunta original. Yo diría que no está mal si usas
println
para imprimir algunas cosas como lo haría la mayoría de la genteprintln
.fuente
En lenguajes de nivel superior como C y C ++, esto es menos problemático que en Java.
En primer lugar, C y C ++ definen la concatenación de cadenas en tiempo de compilación, por lo que puede hacer algo como:
En tal caso, concatenar la cadena no es solo una optimización que puede basarse, generalmente (etc.) depende del compilador que realice. Más bien, es requerido directamente por los estándares C y C ++ (fase 6 de la traducción: "Los tokens literales de cadena adyacentes están concatenados").
Aunque es a expensas de una pequeña complejidad adicional en el compilador y la implementación, C y C ++ hacen un poco más para ocultar la complejidad de producir resultados de manera eficiente para el programador. Java es mucho más parecido al lenguaje ensamblador: cada llamada a se
System.out.println
traduce mucho más directamente a una llamada a la operación subyacente para escribir los datos en la consola. Si desea que el almacenamiento en búfer mejore la eficiencia, debe proporcionarlo por separado.Esto significa, por ejemplo, que en C ++, reescribiendo el ejemplo anterior, a algo como esto:
... normalmente 1 casi no tienen efecto sobre la eficiencia. Cada uso de
cout
simplemente depositaría datos en un búfer. Ese búfer se volcaría a la secuencia subyacente cuando se llenara el búfer, o el código intentara leer la entrada del uso (como constd::cin
).iostream
s también tienen unasync_with_stdio
propiedad que determina si la salida de iostreams está sincronizada con la entrada de estilo C (por ejemplo,getchar
) De forma predeterminada,sync_with_stdio
se establece en verdadero, por lo que si, por ejemplo, escribe enstd::cout
, luego lee a través degetchar
, los datos en los que escribiócout
se enjuagarán cuandogetchar
se llame. Puede establecerlosync_with_stdio
en falso para deshabilitarlo (generalmente para mejorar el rendimiento).sync_with_stdio
También controla un grado de sincronización entre hilos. Si la sincronización está activada (el valor predeterminado), escribir en un iostream desde varios subprocesos puede provocar que los datos de los subprocesos se intercalen, pero evita cualquier condición de carrera. IOW, su programa se ejecutará y producirá salida, pero si más de un hilo escribe en una secuencia a la vez, la mezcla arbitraria de los datos de los diferentes hilos generalmente hará que la salida sea bastante inútil.Si desactiva la sincronización, entonces sincronizar el acceso desde múltiples hilos también se convierte en su responsabilidad. Las escrituras concurrentes de múltiples hilos pueden / conducirán a una carrera de datos, lo que significa que el código tiene un comportamiento indefinido.
Resumen
C ++ por defecto es un intento de equilibrar la velocidad con la seguridad. El resultado es bastante exitoso para código de subproceso único, pero no tanto para el código de subproceso múltiple. El código multiproceso generalmente necesita garantizar que solo un hilo escriba en una secuencia a la vez para producir una salida útil.
1. Es posible desactivar el almacenamiento en búfer para una transmisión, pero en realidad hacerlo es bastante inusual, y cuando / si alguien lo hace, probablemente sea por una razón bastante específica, como garantizar que toda la salida se capture de inmediato a pesar del efecto en el rendimiento . En cualquier caso, esto solo sucede si el código lo hace explícitamente.
fuente
"2^31 - 1 = " + Integer.MAX_VALUE
se almacena como una sola cadena interna (JLS Sec 3.10.5 y 15.28 ).Si bien el rendimiento no es realmente un problema aquí, la mala legibilidad de un montón de
println
declaraciones apunta a un aspecto de diseño que falta.¿Por qué escribimos una secuencia de muchas
println
declaraciones? Si fuera solo un bloque de texto fijo, como un--help
texto en un comando de consola, sería mucho mejor tenerlo como un recurso separado y leerlo y escribirlo en la pantalla a pedido.Pero generalmente es una mezcla de partes dinámicas y estáticas. Digamos que tenemos algunos datos de pedidos desnudos, por un lado, y algunas partes fijas de texto estático, por otro lado, y estas cosas tienen que mezclarse para formar una hoja de confirmación de pedido. Una vez más, también en este caso, es mejor tener un archivo de texto de recursos separado: El recurso sería una plantilla, que contiene algún tipo de símbolos (marcadores de posición), que se reemplazan en tiempo de ejecución por los datos del pedido real.
La separación del lenguaje de programación del lenguaje natural tiene muchas ventajas, entre ellas está la internacionalización: es posible que deba traducir el texto si desea convertirse en multilingüe con su software. Además, ¿por qué debería ser necesario un paso de compilación si solo desea tener una corrección textual, por ejemplo, corregir algunos errores ortográficos?
fuente