C ++ буферы cout и cin, и буферы в целом - PullRequest
15 голосов
/ 14 февраля 2012

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

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

Из текста:

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.

Ответы [ 2 ]

23 голосов
/ 14 февраля 2012

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

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

Действительно ли переопределение 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 only , когда вы действительно хотите очистить вывод. Не используйте std::endl, когда вы на самом деле просто хотите создать конец строки!Просто напишите символ новой строки для последнего!

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

2 голосов
/ 14 февраля 2012

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

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

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

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

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

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

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

...