endl y vaciar el buffer

En el primer libro de C ++ , en el capítulo (1), menciona lo siguiente:

endl es un valor especial, llamado manipulador, que cuando se escribe en una secuencia de salida tiene el efecto de escribir una nueva línea en la salida y borrar el búfer asociado con ese dispositivo . Al borrar el buffer, nos aseguramos de que el usuario vea el resultado escrito en el flujo de inmediato.

¿Qué se entiende por “enjuagar el buffer” aquí?

La salida generalmente se almacena en el búfer antes de que se escriba en el dispositivo previsto. De esta forma, cuando se escribe para ralentizar el acceso a dispositivos (como archivos), no tiene que acceder al dispositivo después de cada carácter.

Enjuagar significa vaciar el búfer y realmente escribirlo en el dispositivo.

Los iostreams de C ++ se almacenan en un búfer, lo que significa que cuando la salida es ostream, el contenido no irá inmediatamente a lo que está detrás de la transmisión, por ejemplo, stdout en el caso de cout. La implementación de la secuencia determina cuándo enviar realmente la parte protegida de la transmisión. Esto se hace por razones de eficiencia, sería muy ineficiente escribir en una red o en un byte de flujo de disco, por medio de un buffer se resuelve este problema.

Sin embargo, esto significa que cuando escribe decir mensajes de depuración en un archivo de registro y su progtwig falla, puede perder parte de los datos que escribió en el archivo de registro a través de la secuencia, ya que una parte del registro puede estar en el búfer de la secuencia y aún no está escrito en el archivo real. Para evitar que esto suceda, debe hacer que la ruta purgue sus búferes mediante una llamada explícita al método de descarga o mediante la conveniencia de endl.

Sin embargo, si solo está escribiendo en un archivo con regularidad, debe usar \ n en lugar de endl para evitar que la transmisión limpie innecesariamente el flujo de cada línea reduciendo su rendimiento.

Editado para incluir esta nota:

cin y cout tienen una relación especial, en la que la lectura desde cin automáticamente desentierra cout de antemano. Esto asegura que, por ejemplo, el mensaje que escribió le avisará al usuario antes de que la lectura de cin esté esperando la entrada. Por lo tanto, incluso en cout normalmente no necesitas endl, pero puedes usar \ n en su lugar. También puede crear relaciones entre otras transmisiones atandolas.

¿Qué se entiende por “enjuagar el buffer” aquí?

std::endl hace que los datos en la memoria de etapas interna de la secuencia (su “buffer”) sean “purgados” (transferidos) al sistema operativo. El comportamiento posterior depende del tipo de dispositivo al que se asigna el flujo, pero, en general, el enjuague dará la apariencia de que los datos se han transferido físicamente al dispositivo asociado. Sin embargo, una pérdida repentina de poder podría vencer la ilusión.

Este enrojecimiento implica una sobrecarga (tiempo perdido) y, por lo tanto, debe minimizarse cuando la velocidad de ejecución es una preocupación importante. Minimizar el impacto general de esta sobrecarga es el propósito fundamental del almacenamiento en memoria intermedia de datos , pero este objective puede ser vencido mediante un enjuague excesivo.


Información de fondo

La E / S de un sistema informático suele ser muy sofisticada y está compuesta de múltiples capas de abstracción. Cada capa puede presentar una cierta cantidad de sobrecarga. El búfer de datos es una forma de reducir esta sobrecarga al minimizar el número de transacciones individuales realizadas entre dos capas del sistema.

  • Almacenamiento en memoria intermedia de la CPU / memoria (almacenamiento en caché) : para una actividad muy alta, incluso el sistema de memoria de acceso aleatorio de una computadora puede convertirse en un cuello de botella. Para abordar esto, la CPU virtualiza los accesos a la memoria al proporcionar capas múltiples de cachés ocultos (los búferes individuales de los cuales se llaman líneas de caché). Estos cachés de procesador almacenan en búfer las escrituras de memoria de su algoritmo (de acuerdo con una política de escritura ) para minimizar los accesos redundantes en el bus de memoria.

  • Almacenamiento en búfer de nivel de aplicación : aunque no siempre es necesario, no es raro que una aplicación asigne trozos de memoria para acumular datos de salida antes de pasarla a la biblioteca de E / S. Esto proporciona la ventaja fundamental de permitir accesos aleatorios (si es necesario), pero una razón importante para hacer esto es que minimiza la sobrecarga asociada con hacer llamadas a la biblioteca, lo que puede consumir mucho más tiempo que simplemente escribir en una matriz de memoria .

  • Almacenamiento intermedio de la biblioteca de E / S : la biblioteca de flujo C ++ IO administra de forma opcional un búfer para cada flujo abierto. Este búfer se utiliza, en particular, para limitar el número de llamadas al sistema del kernel del sistema operativo porque dichas llamadas tienden a tener una sobrecarga no trivial. Este es el buffer que se vacía al usar std::endl .

  • kernel del sistema operativo y controladores de dispositivo : el sistema operativo enruta los datos a un controlador de dispositivo específico (o subsistema) según el dispositivo de salida al que esté conectada la transmisión. En este punto, el comportamiento real puede variar ampliamente según la naturaleza y las características de ese tipo de dispositivo. Por ejemplo, cuando el dispositivo es un disco duro, es posible que el controlador del dispositivo no inicie una transferencia inmediata al dispositivo, sino que mantenga su propio búfer para minimizar aún más las operaciones redundantes (ya que los discos también se escriben de manera más eficiente en fragmentos ) Para eliminar explícitamente los búferes de nivel de kernel, puede ser necesario llamar a una función de nivel de sistema como fsync() on Linux ; incluso el cierre de la secuencia asociada no obliga necesariamente dicha descarga.

    Los dispositivos de salida de ejemplo pueden incluir …

    • una terminal en la máquina local
    • un terminal en una máquina remota (vía SSH o similar)
    • datos que se envían a otra aplicación a través de tuberías o sockets
    • muchas variaciones de dispositivos de almacenamiento masivo y sistemas de archivos asociados, que pueden (de nuevo) conectarse localmente o distribuirse a través de una red
  • Buffers de hardware : hardware específico puede contener sus propios búferes de memoria. Por ejemplo, las unidades de disco duro suelen contener un búfer de disco para (entre otras cosas) permitir que se realicen las escrituras físicas sin que la CPU del sistema se dedique a todo el proceso.

En muchas circunstancias, estas diversas capas de almacenamiento temporal tienden a ser (en cierta medida) redundantes y, por lo tanto, esencialmente exageradas. Sin embargo, el almacenamiento en búfer en cada capa puede proporcionar una gran ganancia en el rendimiento si las otras capas, por cualquier razón, no pueden entregar un almacenamiento en búfer óptimo con respecto a la sobrecarga asociada con cada capa.

Long story short, std::endl solo se dirigió al búfer que es administrado por la biblioteca de la stream de C ++ IO para ese flujo en particular. Después de llamar a std::endl , los datos se habrán trasladado a la administración a nivel de núcleo, y lo que ocurra a continuación con los datos depende de muchos factores.


Cómo evitar la sobrecarga de std::endl

  • Método 1: No use std::endl – use '\n' lugar.
  • Método 2: No use std::endl – use algo como la siguiente versión en su lugar …

 inline std::ostream & endl( std::ostream & os ) { os.put( os.widen('\n') ); // http://en.cppreference.com/w/cpp/io/manip/endl if ( debug_mode ) os.flush(); // supply 'debug_mode' however you want return os; } 

En este ejemplo, proporciona un endl personalizado que se puede llamar con o sin invocación de la llamada interna a flush() (que es lo que fuerza la transferencia al sistema operativo). La habilitación del flush (con la variable debug_mode ) es útil para depurar escenarios en los que desea examinar el resultado (por ejemplo, un archivo de disco) cuando el progtwig ha finalizado antes de cerrar limpiamente los flujos asociados (lo que habría forzado una final descarga del buffer).

Al usar std::cout , el operando utilizado después del operador de salida ( << ) se almacena en un búfer y no se muestra en el stdin (generalmente terminal, o el símbolo del sistema) hasta que se encuentra con std::endl o std::cin , que hace que el buffer se vacíe , en el sentido, muestra / muestra el contenido del buffer en el stdin .

Considera este progtwig:

 #include  #include  int main(void) { std::cout << "Hello, world"; sleep(2); std::cout << std::endl; return 0; } 

El resultado obtenido será:

después de 2 segundos

Hola Mundo