2012-02-14 3 views
14

Может кто-нибудь объяснить концепцию буферов более явно? Я понимаю, что буферы представляют собой структуры данных, где хранятся символы, и место, откуда должны считываться данные. Какова идея сброса буферов?C++ буферы 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» в конце он говорит системе, что нужно писать сразу (подразумевая, в противном случае есть бы и нет?) Что епсИ не используется?

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. 
+0

Этот вопрос был задан и ответил много времени в SO. http://stackoverflow.com/a/4752069/1155650 –

ответ

2

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

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

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

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

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

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

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

21

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

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

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

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

Стандарта C++ библиотеки обычно не требует явного притоки:

  • Поток 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. Когда используются другие механизмы ввода/вывода, может потребоваться явно указать потоки, связанные с потоком. Буфер иногда является проблемой, если программа «сбой» или утверждает во время отладки, поскольку некоторые выходы в поток, возможно, были сделаны, но еще не отправлены. Средством для этого является использование std::unitbuf.

+3

Wow. Я читаю вашу книгу «Стандартная библиотека C++» прямо сейчас! – rbtLong

Смежные вопросы