¿Por qué quiere evitar el enrojecimiento estándar?

Me encontré con una pregunta en Codereview , y en una respuesta los comentarios fueron para evite std::endl porque vacía la secuencia. La cita completa es:

Aconsejo evitar std::endl en general. Además de escribir un nuevo- línea a la secuencia, vacía la secuencia. Usted quiere la nueva línea, pero casi nunca desea eliminar la secuencia, por lo que generalmente es mejor escribir un \ n. En las raras ocasiones en que realmente desee el lavado, hágalo explícitamente: std::cout << "\n" << std::flush;.

El póster lo hizo No explique esto, ni en el post ni en los comentarios. Entonces mi pregunta es simplemente esta:

¿Por qué quiere evitar el enrojecimiento?

Lo que me hizo sentir aún más curioso fue que el cartel dice que es muy raro que quieras tirar la cadena. No tengo ningún problema en imaginar situaciones en las que quieres evitar la descarga, pero todavía pensé que, en general, querrías hacerlo. flush cuando imprime una nueva línea. Después de todo, ¿no es esa la razón por la que std::endl se descarga en primer lugar?

Solo para comentar los votos cerrados de antemano:

No considero que esta opinión esté basada. Lo que debería preferir puede basarse en opiniones, pero hay razones objetivas a tener en cuenta. Las respuestas hasta ahora prueban esto. La descarga afecta el rendimiento.

Comentarios

Respuesta

La respuesta corta y simple es que el uso de std::endl puede ralentizar y reducirá la salida por un margen enorme. De hecho, estoy razonablemente convencido de que std::endl es responsable de la mayoría de la noción de que los iostreams de C ++ son sustancialmente más lentos que los I / O de estilo C.

Por ejemplo, considere un programa como este:

#include <iostream> #include <string> #include <sstream> #include <time.h> #include <iomanip> #include <algorithm> #include <iterator> #include <stdio.h> char fmt[] = "%s\n"; static const int count = 3000000; static char const *const string = "This is a string."; static std::string s = std::string(string) + "\n"; void show_time(void (*f)(), char const *caption) { clock_t start = clock(); f(); clock_t ticks = clock()-start; std::cerr << std::setw(30) << caption << ": " << (double)ticks/CLOCKS_PER_SEC << "\n"; } void use_printf() { for (int i=0; i<count; i++) printf(fmt, string); } void use_puts() { for (int i=0; i<count; i++) puts(string); } void use_cout() { for (int i=0; i<count; i++) std::cout << string << "\n"; } void use_cout_unsync() { std::cout.sync_with_stdio(false); for (int i=0; i<count; i++) std::cout << string << "\n"; std::cout.sync_with_stdio(true); } void use_stringstream() { std::stringstream temp; for (int i=0; i<count; i++) temp << string << "\n"; std::cout << temp.str(); } void use_endl() { for (int i=0; i<count; i++) std::cout << string << std::endl; } void use_fill_n() { std::fill_n(std::ostream_iterator<char const *>(std::cout, "\n"), count, string); } void use_write() { for (int i = 0; i < count; i++) std::cout.write(s.data(), s.size()); } int main() { show_time(use_printf, "Time using printf"); show_time(use_puts, "Time using puts"); show_time(use_cout, "Time using cout (synced)"); show_time(use_cout_unsync, "Time using cout (un-synced)"); show_time(use_stringstream, "Time using stringstream"); show_time(use_endl, "Time using endl"); show_time(use_fill_n, "Time using fill_n"); show_time(use_write, "Time using write"); return 0; } 

Con la salida estándar redirigida a un archivo, esto produce los siguientes resultados:

 Time using printf: 0.208539 Time using puts: 0.103065 Time using cout (synced): 0.241377 Time using cout (un-synced): 0.181853 Time using stringstream: 0.223617 Time using endl: 4.32881 Time using fill_n: 0.209951 Time using write: 0.102781 

El uso de std::endl ralentizó el programa en un factor de aproximadamente 20 en este caso. Si escribiste cadenas más cortas, la ralentización podría / sería aún mayor.

Hay algunos casos en los que realmente y realmente desea descargar una transmisión manualmente, pero honestamente son muy pocos y distantes entre sí.

La mayoría de las veces una secuencia necesita ser vaciada (por ejemplo, imprime un mensaje y luego espera alguna entrada) «sucederá automáticamente a menos que» haya usado cosas como std::tie / o std::sync_with_stdio para evitar eso.

Eso deja solo una pequeña cantidad de situaciones verdaderamente inusuales en las que una buena razón para vaciar un arroyo manualmente. Estos casos son lo suficientemente raros como para que valga la pena usar std::flush cuando suceden, para hacer evidente a cualquiera que lea el código que está descargando la transmisión intencionalmente (y más a menudo que no, probablemente también merezca un comentario sobre por qué este es uno de los raros casos en los que vaciar la transmisión realmente tiene sentido).

Respuesta

Cada vez que un proceso produce una salida, tiene que llamar a una función que realmente hace el trabajo. En la mayoría de los casos, esa función es en última instancia write(2). En un sistema operativo multitarea, la llamada a write() atrapará en el kernel, que tiene que detener el proceso, manejar las E / S, hacer otras cosas mientras se eliminan los bloqueos, poner ponerlo en la cola de espera y volver a ejecutarlo cuando llegue el momento. En conjunto, puede llamar a toda esa actividad gastos generales de llamadas al sistema . Si eso suena a mucho, lo es.

Limpiar un flujo almacenado en búfer * después de escribir una pequeña cantidad de datos o no tener ningún búfer genera esa sobrecarga cada vez que lo hace:

 1\n (System call that writes two bytes) 2\n (System call that writes two bytes) 3\n (System call that writes two bytes) 4\n (System call that writes two bytes) 5\n (System call that writes two bytes)  

Así se hacía en los primeros días hasta que alguien descubrió que estaba quemando un mucho tiempo del sistema. La sobrecarga podría reducirse acumulando la salida en un búfer hasta que se llene o el programa decida que debe enviarse inmediatamente.(Es posible que desee hacer lo último si «está produciendo una salida esporádica que necesita ser vista o consumida). Evitar una descarga al final de cada línea reduce la cantidad de llamadas al sistema y la sobrecarga incurrida:

 1\n 2\n 3\n 4\n 5\n (Flush) (System call that writes ten bytes)  

* Tenga en cuenta que el concepto de salida estándar es un descriptor de archivo asociado con un proceso y dado un número bien conocido. Esto difiere del stdout definido por C, C ++ y otros, que son identificadores para implementaciones de una transmisión almacenada en búfer que viven completamente en el área de usuario y escriben en el salida estándar. La llamada al sistema write() no se almacena en búfer.

Comentarios

  • Si y cuándo vaciar depende del contexto en el que se usará esa salida. Un programa orientado al rendimiento solo debe descargar cuando sus búferes están llenos. Un programa sensible a la latencia debe descargar más a menudo. Por ejemplo, si la salida va a una consola, Lavaré después de cada nueva línea. Los programas interactivos (que muestran algún mensaje de entrada) deben descargarse inmediatamente, incluso si la línea aún no está llena.
  • » si la salida va a una consola, todavía se descargaría después de cada nueva línea. » Verdadero, pero si la salida va a una consola, hay una descarga automática después de cada nueva línea. No es necesario hacer eso explícitamente.
  • @amon Aún querrá fusionar la salida consecutiva, ya que el vaciado oportuna, es decir, antes de solicitar la entrada y sin demoras vencidas, es suficiente. Es cierto que es mejor vaciar una vez con demasiada frecuencia que tener demasiado retraso en mostrar la salida, o invertir demasiado trabajo en optimizar …
  • Un truco que a veces usan los búfer es reiniciar / iniciar un temporizador cuando llegan cosas nuevas y descarga cuando se acaba el temporizador.

Responder

Por qué se debe evitar la descarga:

Porque IO funciona mejor cuando el sistema operativo puede trabajar con cantidades relativamente grandes de datos. Los vaciados regulares con pequeñas cantidades de datos provocan ralentizaciones, a veces de forma muy significativa.

Por qué casi nunca se debe vaciar manualmente:

Hay descargas automáticas que cubren la mayoría de los casos de uso. Por ejemplo, si un programa escribe en la consola, el sistema se vacía por defecto después de cada nueva línea. O si escribe en un archivo, los datos se escriben una vez que hay suficientes datos para escribirlos de una vez, y también cuando se cierra el archivo.

Cuando debe vaciar manualmente:

Si explícitamente necesita actualizar la salida inmediatamente. Ejemplo: si crea una ruleta o una barra de progreso que sobrescribe repetidamente la línea actual. O si imprime en un archivo y realmente desea que el archivo se actualice en momentos específicos.

Comentarios

  • El sistema no puede vaciar ningún búfer de usuario. ¿O subsume las bibliotecas, especialmente la biblioteca estándar, también en el » system «? Por supuesto, stdin y stdout generalmente están acoplados al menos si ambos usan la misma consola.
  • Sí, No ‘ pensé que sería una buena idea entrar en demasiados detalles sobre cómo se dividen las cosas entre las bibliotecas del espacio de usuario y el espacio del kernel.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *