С++ cout и cin буферы и буферы в целом

Может кто-нибудь объяснить концепцию буферов несколько более явно? Я понимаю, что буферы представляют собой структуры данных, где хранятся символы, и место, откуда должны считываться данные. Какова идея сброса буферов?

Когда буфера очищается, это относится к акту записи сохраненных в нем символов?

Из текста:

To avoid the overhead of writing in response to each output request, the library uses the 
buffer to accumulate the characters to be written, and flushes the buffer, by writing its
contents to the output device, only when necessary. By doing so, it can combine several 
output operations into a single write.

Когда речь идет о "промывке", это почти заставляет звук звучать так, как если бы буфер записывался, но также удалялся одновременно. Просто спекуляция.

Итак, для того, чтобы писать на экране, требуется флеш-буфер?

When our program writes its prompt to cout, that output goes into the buffer associated
with the standard output stream. Next, we attempt to read from cin. This read flushes
the cout buffer, so we are assured that our user will see the prompt.

Здесь звучит так, как будто с помощью "endl" в конце он сообщает системе, что ему нужно немедленно написать (подразумевая, что иначе это не так?) Что такое endl не используется?

Writing the value of std::endl ends the line of 
output, and then flushes the buffer, which forces the system to write to the output 
stream immediately.

Ответы

Ответ 1

Основная идея буферизации - объединить операции в более крупные куски: вместо чтения небольшого количества байтов прочитайте всю страницу и сделайте ее доступной по запросу; вместо того, чтобы писать небольшое количество байтов, буферизировать их и написать целую страницу или когда запись явно запрашивается. По сути, это важная оптимизация производительности. Это довольно четкое сокращение операций ввода-вывода, но в целом применяется и для других целей: обработка нескольких блоков одновременно заканчивается быстрее, чем обработка отдельных блоков.

Что касается промывки ввода-вывода, то это означает, что в настоящее время буферизованные байты записываются в пункт назначения - что бы это ни значило на практике. Для С++ IOStreams поток потока сводится к вызову функции-члена std::ostream::flush(), которая, в свою очередь, вызывает std::streambuf::pubsync() в связанном потоковом буфере (это игнорирует детали, которые потоки фактически являются шаблонами классов, например std::basic_ostream<cT, traits>; обсуждение не имеет значения, что они являются шаблонами классов): базовый класс std::streambuf - это абстракция С++ о том, как должен обрабатываться определенный поток. Он концептуально состоит из входного и выходного буфера плюс виртуальная функция, отвечающая за чтение или запись буферов, соответственно. Функция std::streambuf::pubsync() вызывает виртуальную функцию std::streambuf::sync(), которая должна быть переопределена для каждого буфера потока, который потенциально буферизирует символы. То есть то, что на самом деле означает смывание, зависит от того, как реализована эта виртуальная функция.

Независимо от того, что переопределение sync() что-то делает и что он делает, явно зависит от представления буфера потока. Например, для std::filebuf, который отвечает за чтение или запись в файл, sync() записывает текущее содержимое буфера и удаляет покрасневшие символы из буфера. Учитывая, что файл не может действительно представлять физический файл, но, например, файл. именованный канал для связи с другим процессом, это разумное поведение. С другой стороны, очистка a std::stringbuf, который является буфером потока, используемым для реализации записи в std::string, используемом, например. на std::ostringstream фактически ничего не делает: строка целиком находится внутри программы, а std::string, представляющая ее значение, создается при вызове функции-члена std::stringbuf::str(). Для пользовательских потоковых буферов существует много разных способов поведения. Обычный класс потоковых буферов фильтрует выход каким-то образом перед его передачей в другой буфер потока (например, буфер регистрации может добавлять отметку времени после каждой новой строки). Обычно они просто вызывают функцию std::streambuf::pubsync() для следующих буферов потока.

Таким образом, описания фактического поведения обычно сохраняются довольно расплывчатыми, потому что не совсем ясно, что именно происходит. Понятно, что сброс потока или вызов pubsync() в буфере потока должен обновлять внешний адрес символа в соответствии с текущим внутренним состоянием. Как правило, это означает пересылку буферизованных символов в текущий момент и удаление их из внутреннего буфера. На этом этапе стоит отметить, что буфер обычно также записывается, когда он просто заполнен. Когда он заполняется, зависит, опять же, от конкретного буфера потока. Хорошая реализация std::filebuf будет по существу заполнять буфер байтов, соответствующий размеру базовой страницы (или ее нескольких), а затем писать полные страницы, сводя к минимуму количество операций ввода-вывода (это на самом деле относительно сложно сделать потому что размеры буфера различны между различными файловыми системами и в зависимости от кодировки, используемой при записи количества созданных байтов, не могут быть легко оценены).

Стандартная библиотека С++ обычно не требует явных сбросов:

  • Поток std::cerr настроен для автоматического сброса любого выхода, созданного после каждой вызываемой операции вывода. Это результат того, что флаг форматирования std::ios_base::unitbuf установлен по умолчанию. Чтобы отключить это, вы можете использовать std::cerr << std::nounitbuf или можете использовать только std::clog, который пишет в тот же пункт назначения, но не выполняет эту очистку.
  • При чтении из std::istream "связанный" std::ostream, если он есть, очищается. По умолчанию std::cout привязан к std::cin. Если вы хотите настроить привязанный std::ostream самостоятельно, вы можете использовать, например. in.tie(&out) или, если вы хотите удалить связанный std::ostream, вы можете использовать, например. std::cin.tie(0).
  • Когда std::ostream уничтожается (или когда он обычно уничтожается, если std::ostream является одним из стандартных потоков), std::ostream очищается.
  • Когда буфера потока будет переполняться, вызывается виртуальная функция std::streambuf::overflow(), которая обычно записывает буфер текущего буфера (плюс переданный символ, если есть) в пункт назначения. Это часто делается путем вызова sync() для очистки текущего буфера, но то, что делается точно, опять же зависит от конкретного потока буфера потока.

Вы также можете явно запросить очистку std::ostream:

  • Вы можете вызвать функцию-член std::ostream::flush(), например. std::cout.flush().
  • Вы можете вызвать функцию-член std::streambuf::pubsync(), например. std::cout.rdbuf()->pubsync() (при условии, что настроен буфер потока, вызов std::ostream::flush() ничего не сделает, если нет буфера потока).
  • Вы можете использовать манипулятор std::flush, например. std::cout << std::flush.
  • Вы можете использовать манипулятор std::endl, например. std::cout << std::endl, чтобы сначала написать символ новой строки, за которым следует промывка потока. Обратите внимание, что вы должны использовать std::endl только, когда вы действительно хотите очистить вывод. Не используйте std::endl, когда вы просто хотите создать конец строки! Просто напишите символ новой строки для последнего!

В любом случае, если вы просто пишете пару символов, которые не вызывают переполнение буфера буфера потока, с ними ничего не произойдет, пока они не будут либо неявно, либо явно сброшены. Как правило, это не проблема, потому что нормальный случай, когда буферизованный вывод должен стать доступным, т.е. При чтении из std::cin, обрабатывается std::cout как tie() d до std::cin. Когда используются другие механизмы ввода-вывода, может потребоваться явно связанный поток tie(). Буфер иногда является проблемой, если программа "сбой" или утверждает во время отладки, поскольку некоторые выходы в поток, возможно, были сделаны, но еще не отправлены. Средством для этого является использование std::unitbuf.

Ответ 2

Подумайте, что произойдет, если каждый раз, когда вы "написали" байт в файл на диске, ваша программа фактически вышла на диск, прочитала в текущем секторе/кластере/блоке, изменила один байт, затем записала его обратно на физический диск.

Я бы сказал, что ваше программное обеспечение лучше всего описывается как "ледниковое" с точки зрения производительности: -)

Буферизация - это средство дозирования чтения и записи, чтобы сделать их более эффективными.

Например, если вы пишете файл на диске, он может дождаться, пока у вас есть полный блок 4K, прежде чем записывать его на диск.

Когда вы читаете, он может получить блок 4K, даже если вы попросили только десять байт, исходя из предположения, что вы можете попросить остальных в ближайшее время.

Промывка при записи происходит неявно после того, как кеш заполнен или явно запрошен (либо с помощью флеш-вызова, либо при закрытии файла).

Обратите внимание, что эта буферизация файлов - это только один вид буферизации, эта концепция может использоваться в любом месте, где эффективность может быть достигнута путем "chunking" вверх по чтению и записи.